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#的不同用法:
- 在C++的名词成员变量,在C#中称为字段,字段在C++中不能初始化,但在C#中可以直接初始化,成为默认值。
//这样在C++中是会报错的
class Myclass
{
public double x=10, y=10086, z=131;
public Myclass()
{
}
}
- 构造函数: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