C++程序员光速入门C#(二):流程控制、函数、类

Jacob的C++程序员光速入门C#系列:
C++程序员光速入门C#(一):总览、数据类型、运算符
C++程序员光速入门C#(二):流程控制、函数、类
C++程序员光速入门C#(三):继承、泛型、异常

一.流程控制

1.if/if..else语句

这个语句和C++没有区别

2.switch

选择分支语句和C++用法也类似,但是C#中对它的格式更加严格了,每个case语句后面都必须跟上break,不然就是错误的语句。但是也有例外,可以使用臭名昭著的goto语句,跳转到下个case语句,这样编译器是不会报错的。

当然如果你真的希望有多个条件执行相同语句的话,可以省略写成下面的方法:

switch (char c) 
{ 
    case ‘ua’: 
    case ‘us’: 
    case ‘en’:string language=”English”;break; 
} 
2.switch

C++中的3种循环语句:for , while , do…while 和C#中是相同的 。但是C#中添加了一种循环语句,在对数组的输出方面的使用非常灵活, 它就是:foreach语句 。它的语法格式如下

foreach (<变量类型> <变量名>  in  <数组名>)  {} 

不管是一维还是多维数组,操作都一样方便,它会自动知道数组的大小对其操作,我们不需要关心它是否会溢出。

C#中现在增加了交错数组,对它的操作,foreach也可以做到,但格式不同,看下面的例子。

using System; 
namespace ConsoleApplication1 
{ 
    class Class1 
    { 
        static void Main(string[] args) 
        { 
           //定义多维数组
            int[,] a1=new int[2,2]{{1,2},{3,4}}; 
            foreach (int b1 in a1) 
            { 
                Console.WriteLine ("{0}",b1);//对多维数组的操作 
            } 
            //定义交错数组
            int[][] a2=new int[2][]; 
            a2[0]=new int[2]{0,1}; 
            a2[1]=new int[3]{3,4,5}; 
            foreach (int[] b20 in a2) 
            { 
                foreach (int b21 in b20)
                { 
                    Console.WriteLine ("{0}",b21);//对交错数组的操作 
                }
            } 
        } 
    } 
} 

对于变长数组的如此操作,是因为a2包含的是一个int[]元素,而不是int元素,想想我们之前讲的交错数组也叫数组的数组,其实是2个数组的欠套。

现在我们再来看一个对foreach的使用:

//错误例子
int[,] a1=new int[2,2]{{1,2},{3,4}}; 
foreach (int b1 in a1) 
{ 
    b1=3;   //错误 !!!
} 

还记得上面我说的一句话吗?"在对数组的输出方面的使用非常灵活”。也许你已经看出来了,是的,foreach语句只能对数据进行输出,因为它是只读的,不能改变任何数组元素的值。

Qt5中定义了一个关键字,也叫foreach。它的格式是:
foreach (varItem , Items) //中间是逗号
而在C++11标准中,也加了一个foreach,格式是:
foreach (varItem : Items) //中间是冒号
跟C#的foreach也是类似的,也不能修改数组元素。

4.跳转语句

C#中的跳转语句和C++中一样,有:goto , return , break ,continue 。

二.函数

讲到C#的函数,觉得有点别扭,因为现在的所以函数都是包含在一个类当中了,这样就和C++中类的方法的用法相似了,所以在C++中的标准函数需要定义的过程也没有了,这样确切的说函数在C#中已经不复存在了,都变成方法了嘛!

首先我们来看一下C#方法的使用的例子。

/***********************C#程序***************************/
using System; 
namespace ConsoleApplication1 
{ 
 
    class Class1 
    { 
        static void Main(string[] args) 
        { 
            Write();     
        } 
        static void Write() 
        {    
            Console.WriteLine ("This is Text"); 
        } 
    } 
} 

需要反复强调的是:C#是一门完全面向对象的语言!如果要以面向过程的思路定义和使用函数的话,就要在一个类中定义一个静态函数,使用static关键字!

1.参数

C#的方法和C++中的函数在返回值,参数传递方面是一样的,可变参数,默认参数也都有,唯一的区别是引用的传递。 也许你想起来上面所讲,C#中取消了&操作符,这意味着没有了引用传递了,其实不是没有,而是不用&,改成了ref 。

class Class1 
{ 
    static void Main(string[] args) 
    { 
        int a=1,b=2; 
        Console.WriteLine ("{0} {1}",a,b); 
        change(ref a,ref b) ; 
        Console.WriteLine ("{0} {1}",a,b); 
    } 
    static void change(ref int a,ref int b) 
    {    
        int c; 
        c=a; 
        a=b; 
        b=c; 
    } 
} 
/*******************
输出结果: 
1         2 
2         1 
********************/

ref参数需要在函数外初始化传入的变量。out参数则可以不需要初始化。

int a;
func(out a);//不会报错,ref参数会报错
2.委托

委托的概念可以类比C++的函数指针,但某些方面有差别。用法看下如下例子。先看C++的函数指针。

/**********************C++程序*************************/
#include <iostream>

using namespace std;

double (*process)(double num1, double num2);

double add(double num1, double num2)
{
    return num1+num2;
}

double odd(double num1, double num2)
{
    return num1-num2;
}


int main()
{
    bool m_bool=true; 
    double num; 
    if (m_bool) 
    { 
        process = add;
    } 
    else 
    { 
        process = odd;
    } 
    num=process(10,5);  // 结果是15
    cout << "num=" << num << endl;
    
    m_bool=false; 
    if (m_bool) 
    { 
        process = add;
    } 
    else 
    { 
        process = odd;
    } 
    num=process(10,5);  //结果是5
    cout << "num=" << num << endl;

    return 0;
}

很简单的函数指针用法。当然也可以用typedef一个函数指针类型,熟练的C++程序员应该都懂。C++11标准中添加了decltype关键字,也可以用来创建函数指针类型,这里就不再赘述了。

下面看下C#中的委托。

/************************C#程序**************************/
using System; 
namespace ConsoleApplication1 
{ 
    class Class1 
    { 
        delegate double chooes(double num1,double num2); //声明委托
        static double add(double num1,double num2) 
        { 
            return num1+num2; 
        } 
        static double odd(double num1,double num2) 
        { 
            return num1-num2; 
        } 
        static void Main(string[] args) 
        { 
            chooes process; 
            bool m_bool=true; 
            double num; 
            if (m_bool) 
            { 
                process =new chooes (add); 
            } 
            else 
            { 
                process =new chooes (odd); 
            } 
            num=process(10,5); 
            Console.WriteLine ("{0}",num);  //结果是15
             
            m_bool=false; 
            if (m_bool) 
            { 
                process =new chooes (add); 
            } 
            else 
            { 
                process =new chooes (odd); 
            } 
            num=process(10,5); 
            Console.WriteLine ("{0}",num); //结果是5
        } 
    } 
} 

首先注意到的是delegate关键字。delegate关键字类似与用C++的typedef关键字定义了一个函数指针类型,只有符合函数签名的函数才能被传入。

需要注意的是,C#中的委托对象是真正的对象,而C++中的函数指针变量只是函数的入口地址

C#中声明了一个委托对象之后,要用new关键字在堆栈上开辟空间。

//C#程序
chooes process; 
process =new chooes (add); 

而C/C++中的函数指针只是指针,存储的是函数的入口地址。

//C++程序
process = add;//process = &add;也可以

所以一旦函数指针指向的内存区域发生改变,就可能发生意料之外的事情。所以C#的委托更加安全。

还有一点需要注意的是,C#的委托赋值为一个类方法,只要符合函数签名。而C++的函数指针则需要一些额外的声明。看下以下C++程序。

/*****************C++程序******************/
#include <iostream>

using namespace std;

class operation
{
public:
    double add(double num1, double num2)
    {
        return num1+num2;
    }

    double odd(double num1, double num2)
    {
        return num1-num2;
    }
};

double (operation::* process)(double num1, double num2);

int main()
{
    bool m_bool=true; 
    double num; 
    operation *op = new operation;
    if (m_bool) 
    { 
        //跟普通函数不一样,一定要加&
        process = &operation::add;
    } 
    else 
    { 
        process = &operation::odd;
    } 
    //一定要加*然后括起来
    num=(op->*process)(10,5);
    cout << "num=" << num << endl;
    
    m_bool=false; 
    if (m_bool) 
    { 
        process = &operation::add;
    } 
    else 
    { 
        process = &operation::odd;
    } 
    num=(op->*process)(10,5);
    cout << "num=" << num << endl;

    return 0;
}

可以看到,声明函数指针变量时签名要加上类的作用域名。大概是C++编译器会将类成员函数的签名加上一个this指针参数,如 double (*)(operation:: this,double num1, double num2);.。而C#的委托就不需要这样了,直接new就完事了。

三.类与对象

对于面向对象语言来说,类是重头戏,C#中类的使用和C++中类似,在细节方面有些差别。

class myclass:mybasicclass   //注意基类不需要写限定符 
{ 
    private int a; 
    public int num() 
     { 
         return 0; 
     } 
} 
public class mybasicclass 
{ 
} 

这里写出了一个类的限定符public,C#还有的限定符:

internal:只能在当前工程中使用,当class前缺省限制符时就默认为此限制符
public:可以在任何工程中使用
abstract(抽象的):类不能实例化,只能继承,可以有抽象成员
sealed (密封的):不能继承

关键字能够相互组合。

我们看到,C#中没有私有继承之类的概念,类成员的访问控制权限在继承过程中不会改变。(事实上C++也很少用到私有继承)。

首先需要知道一点,C#中只能有一个基类,它不支持多重继承。在前文也已经说过,object类是C#中最高的类,如果一个类没有基类,它就隐式的继承了object类,在类中都可以使用object提供的方法.。

也许你还记得在C++中,类里面定义的方法的具体实现是可以在类的外部实现的,但在C#中已经不允许,C#规定所有的代码都必须在类中。

从上面的例子中还能发现一个不同点就算是:在类定义结束的时候没有了分号。

从现在开始,从细节上阐述C#的不同用法:

  1. 在C++的名词成员变量,在C#中称为字段,字段在C++中不能初始化,但在C#中可以直接初始化,成为默认值。
//这样在C++中是会报错的
class Myclass
{
    public double x=10, y=10086, z=131;
    public Myclass()
    {
    }
}
  1. 构造函数:C#添加了一种静态构造函数
public class mybasicclass 
{ 
    static int a;
    static mybasicclass() 
    { 
        a=10086;
    } 
} 

它不需要限定符,它的作用是在程序运行时对静态变量进行赋值。它什么时候调用是不能确定的,CLR接手了这个过程,程序员不要瞎操心(对,说的就是啥都想管的C++程序员。。)。

3.析构函数(终结器):它和C++的概念已经完全不同了,因为C#内部有了内存自动收集机制,能够对不用的内存进行回收,所以不需要在析构函数中释放内存了,一般情况下不需要重写析构函数,也不能显式调用析构函数。如果想像C++一样自己管理内存,可以让类去实现一个叫做IDispoable的接口。接口的概念会在以后提到。

4.虚函数:C#的虚函数同C++在语法上有区别,但是含义还是一样的。C#中基类的虚拟函数使用关键字virtual,在派生类中使用override

5.运算符重载:C#中运算符重载和C++原理基本相同,区别在于有些运算符C#已经不允许重载了。不能重载的运算符有:-= , += ,/= ,*= ,%=, 按位赋值运算符(&= ……) ,布尔运算符(&&……),赋值运算符(=)。另外语法也略有不同。看下C#的例子。

namespace MyVector
{
    struct Vector
    {
        public double x, y, z;

        //构造函数,没区别
        public Vector(double x,double y,double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        //复制构造函数,和C++也莫得区别
        public Vector(Vector rhs)
        {
            this.x = rhs.x;
            this.y = rhs.y;
            this.z = rhs.z;
        }

        //运算符重载
        //必须是public static类型,且二元运算符有两个参数,一个左值一个右值
        public static Vector operator + (Vector lhs, Vector rhs)
        {
            Vector result = new Vector(lhs);
            result.x += rhs.x;
            result.y += rhs.y;
            result.z += rhs.z;

            return result;
        }
    }
}

语法格式是 public static 返回类型 operator 运算符 (参数)。

可以看到,C#中对于运算符重载的语法比较合理,对于二元运算符的重载,参数中包括了左值和右值。C++中如果出现左值是其他类型的情况(比如说 int + Vector ),就要用到友元函数的特性,语法感觉十分丑陋。。。

同时在C#中还有一个硬性的规定,比如重载了运算符> 就必须重载< ,重载了== ,就必须重载 != ,重载了>=就要重载<=,反之亦然。也就是说,比较运算符要成对地重载,不然编译器就会报错。


可重载和不可重载的运算符

6.属性:C#中属性的概念相当于C++中给数据成员定义它的get方法和set方法。语法如下。

private int age;
public int Age
{
    get
    {
        return age;
    }
    set
    {
        age = value;
    }
}

get和set称为访问器。当我们想让这个属性不能被修改时,可以定义只读属性,不要set访问器就可以了。

private int age;
public int Age
{
    get
    {
        return age;
    }
}

如果访问器没有任何的逻辑,就可以使用自动实现的属性,语法如下。

private int age;
public int Age { get; set; }
//public int Age { get; } //只读
//public int Age { get; private set; } //不能外部修改

7.其他:partial关键字允许把类、结构、方法和接口的定义放在多个文件中,称为部分类。static关键字也可以修饰类,称为静态类。如果类中出现非静态成员,编译器就会报错。

暂时先写到这里,关于继承以及其它高级特性下一篇会讲到。说实话原博客的内容一方面有一些错误,需要大量的翻阅资料去确认和修正;另一方面这么多年过去,C#和C++都成长了不少,要讲的内容实在是有点多,而笔者的能力实在有限。只求少点错误,别耽误可爱的网友吧😭。

Reference:
QT中foreach的使用
C++程序员快速学习C#---(二)
C# vs C++之一:委托 vs 函数指针
被淡忘的C#析构函数
C# 运算符重载
C#高级编程 ——Christian Nagel & Jay Glynn & Morgan Skinner

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,487评论 1 51
  • 3. 类设计者工具 3.1 拷贝控制 五种函数拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符析构函数拷贝和移...
    王侦阅读 1,770评论 0 1
  • C++类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心...
    863cda997e42阅读 621评论 0 4
  • 重新系统学习下C++;但是还是少了好多知识点;socket;unix;stl;boost等; C++ 教程 | 菜...
    kakukeme阅读 19,721评论 0 50
  • 1.面向对象的程序设计思想是什么? 答:把数据结构和对数据结构进行操作的方法封装形成一个个的对象。 2.什么是类?...
    少帅yangjie阅读 4,983评论 0 14