}
C#语言除了public、private和protected的概念与C++语言的同名分区保留字基本相同以外(C#语言的public还允许其它工程访问),还另外定义了专门用于确定自身访问目的新保留字internal。
该保留字作为成员数据的附加类别示意编译器只允许本工程或指定工程内的对象访问该成员数据。
⒉类中成员数据的初始化
1构造函数(Constructorfunction)与析构函数(Deconstructorfunction)
在对象生成的同时被用来为对象动态地开辟内存空间并对对象的成员数据进行初始化的专用函数称为构造函数。
在对象撤消的同时被用来释放对象初始时的内存空间的专用函数称为析构函数。
·构造函数名与类名相同且不得使用任何返回值类型声明;
·无任何参数的构造函数又称为缺省构造函数;
·构造函数即使不显性的写出也会由编译器强制插入一个缺省构造函数;
·每当构造函数被执行一次便会生成一个对象;
·隐式声明的缺省构造函数是公开的内联函数(仅适用于C++语言);
·当一个类中含有多个重载的构造函数时,对象定义的格式将是对其引用的区别标志;
例6:
classAclassA
{{
inti;inti;
public:
A(){i=0;}
A(){i=0;}A(intx){i=x;}
A(intx){i=x;}staticvoidMain()
};{
voidmain()Aa=newA();
{Aa,b(0);}}
};
·析构函数的格式要求与构造函数相同,只是在名字的前方附加“~”;
·同命名空间或作用域内的对象的析构函数在被执行一次后将撤消该对象的活化状态,但仍保留对象在内存中驻留,因而该对象仍可以正常的响应消息的传递(但在其命名空间或作用域撤消时不会留下碎片);
·当类对象所在的命名空间或作用域被撤消时,处于该命名空间或作用域内的对象的析构函数会被自动执行一次来完成撤消对象的内存驻留;
·一个类中有且仅能有一个析构函数(意即:
不得使用任何参数且没有重载的性能);
·析构函数只能释放一个对象生成时所获得的动态内存空间;
例7:
#include
usingnamespacestd;
classA
{
int*p;
public:
A():
p(NULL){}
A(intx){p=newint[x];}
~A(){if(p)deletep;}
};
voidmain()
{Aa,b(10);}
由于C#语言对指针采取了严格的限制手段,因而不支持对象利用指针动态占用内存技术。
C#语言推荐使用数组实现上述技术。
②C++语言的成员初始化表(MemberInitiationTable)
·成员初始化表位于构造函数的声明体与定义体之间并用冒号连接;
·成员初始化表专门用来对对象内仅需进行一次初始化的成员数据进行初始化(包括常量、引用和基类的成员数据),也可以对其它的成员数据进行初始化;
·成员初始化表的基本格式为:
成员数据名1(参数名1),……成员数据名n(参数名n)
例8:
classA
{
constintc;
int&j;
public:
A(intx):
c(x),j((int)c){}
};
③拷贝型构造函数
与一般成员数据的初始化相比,对象中指针成员数据的初始化要考虑的内容要复杂一些。
当对象中指针成员值要指向对象以外的位置时就必须使用拷贝型构造函数来(复制)初始化。
C++语言拷贝型构造函数的通用声明格式如下:
构造函数名([const]同类数据类型引用名,…);//必须是第一参数
例9:
#includeusingSystem;
classAclassA
{{
char*p;strings;
public:
A(stringx){s=x;}
A(char*s)A(Ax){s=x.s;}
{~A(){}
p=newchar[strlen(s)+1];stringget(){returns;}
strcpy(p,s);staticvoidMain()
}{
A(A&s)Aa=newA("Hello!
");
{Ab=newA(a);//Ab=a;
p=newchar[strlen(s.p)+1];Console.Write(b.get());
strcpy(p,s.p);}
}};
~A(){if(p)deletep;}
};
voidmain()
{
char*pi="ABCDEF";
Aa1(pi);
Aa2(a1);
}
综上所述,C++语言类对象成员数据的初始化必须依赖构造函数来实现。
但C#语言由于没有诸如C++语言成员初始化表一类的技术手段,所以允许在类的声明中直接对成员数据进行初始化,已使得其对象生成后,其成员已被设置为默认数值。
这种技术变异主要是为了解决C#语言的代码在运行期间所发生的消息来往中对空参数值比较忌讳的问题。
例10:
C#语言示例
usingSystem;
classA
{
strings="abcd";
inti=0;
A(){}
A(stringx){s=x;}
~A(){}
stringget(){returns;}
intgetdata(){returni;}
staticvoidMain()
{
Aa=newA();
Console.Write(a.get());
Console.Write(a.getdata());
}
};
④C++和C#语言成员数据常数附加类别的使用
在C++语言中存在一种常数附加类别—const。
该类别可以出现在函数的三个不同位置而产生三种不同的作用。
⑴禁止对函数的返回值进行回写
const函数声明;
⑵禁止对函数的参数进行回写
函数声明(const参数1,……const参数i);
⑶禁止对对象内的任何成员数据进行改写
函数声明(参数表)const;
常数附加类别—const在C#语言中只能用来定义成员数据并直接进行一次性赋值。
但若将const改为readonly,则这类成员数据除了在定义时直接进行赋值外,还可以在构造函数中予以再赋值。
⑤类中的静态成员与枚举成员
静态成员与枚举成员在一个类中只有一份,恰是理论上的类属性和类方法的具体实现。
由于这一特点,其初始化也较为特殊。
⑴静态成员数据与枚举成员数据的初始化
类中静态成员数据与枚举成员数据的声明格式与一般的声明格式相同,但鉴于静态成员数据与在类中的唯一性特点,其值的初始化一律使用作用域符号置于类外(枚举成员和静态常数成员数据的初始化例外)。
其在C++语言中的通用格式如下:
类型类名∷成员名=值;
类型类名∷枚举成员名={值表};
在C#语言中的静态成员数据初始化则直接写在类内。
枚举成员的定义也要求放置在类内,与C++语言不同的是C#语言中的枚举成员必须有确定的名字。
其他的语法规则与C++语言中的枚举成员是一致的。
例11:
C++语言示例
#include
usingnamespacestd;
classA
{
staticinti;
public:
A(){i++;}
intlist(){returni;}
};
intA:
:
i=0;
voidmain()
{
Aa1,a2,a3;
cout<}
例12:
C#语言示例
usingSystem;
classA
{
staticinti=0;
A(){i++;}
intlist(){returni;}
staticvoidMain()
{
Aa=newA(),b=newA(),c=newA();
Console.Write(a.list());//显示?
}
};
例13:
枚举数据成员的C++语言示例
#include
usingnamespacestd;
classColor
{
public:
enum{black=1,lightblack=1,blue=4};
};
voidmain()
{
inti=Color:
:
black;
unsignedlongk=Color:
:
lightblack;
floatf=Color:
:
lightblack;
cout<
}
例14:
枚举数据成员的C#语言示例
usingSystem;
classA
{
staticvoidMain()
{
intk=(int)B.Color.blue;
Console.WriteLine(B.Color.blue+"="+k.ToString());//显示blue=4
}
};
classB
{
internalenumColor{black=0,lightblack=1,blue=4};//internal附加类别是必须的
};
⑵静态成员函数的声明和引用
静态成员函数的声明并无特殊性,其定义格式与静态成员数据类同。
但鉴于静态成员函数为类属,所以呼叫的消息格式也应改为类标识;
例15:
C++语言示例
#include
usingnamespacestd;
classA
{
staticinti;
public:
A(){i++;}
staticintlist(){returni;}
};
intA:
:
i=0;
voidmain()
{
Aa1,a2,a3;
cout<:
list();
}
例16:
C#语言示例
usingSystem;
classA
{
staticinti=0;
A(){i++;}
staticintlist(){returni;}
staticvoidMain()
{
Aa=newA(),b=newA(),c=newA();
Console.Write(A.list());//显示?
}
};
C#语言中的启动函数Main()必须安放在一个类内且必须是一个静态成员函数。
这与其不支持面向过程的设计思维是一致的。
操作系统首先就是从Main()这个类方法开始启动整个面向对象的程序的。
⑥对象的自我识别—this指针的应用
在一个现实的系统内多数对象没有显性的名字,而是用指针来标识的。
消息的返回值也常常是对象自身的首地址,因此对象能随时得到自身的首地址就显得非常重要了。
C++和C#语言为每个存在的对象保留了一个标识其首地址指针名:
this。
在C#语言中的this指针名是以引用的方式传递的。
例17:
C++语言示例C#语言示例
#includeusingSystem;
usingnamespacestd;classA
classA{
{inti=0;
inti;intSet(intx){returni=x;}
public:
AGetmyad(){returnthis;}
A(intx):
i(x){}staticvoidMain()
A*myad(){returnthis;}{
intadd(intx=0){returni+=x;}Aa=newA();
};Ap=a.Getmyad();//p是引用
voidmain()Console.Write(p.Set
(1));
{}
Aa1(0),a2
(1);};
A*p=a1.myad();
cout<add
(1)<}
⑦并行处理对数据成员的影响
对象化的程序系统常常要面对多个线程对同一个对象的某个成员数据进行访问。
C++语言是利用线程自身相互约束的机制来避免发生冲突。
C#语言则可以利用语言自身的技术手段(lock)来防止冲突。
例18:
usingSystem;
usingSystem.Threading;//使用系统命名空间中的线程对象
classA
{
Bx;
A(){x=newB();}
voidsub()
{
lock(x)
{
x.i--;
Thread.Sleep
(1);
Console.WriteLine("i="+x.i.ToString());
}
}
staticvoidMain()
{
Aa=newA();
Threadone=newThread(newThreadStart(a.sub));
Threadtwo=newThread(newThreadStart(a.sub));
one.Start();
two.Start();
}
};
classB
{
internalinti=5;
internalB(){}
};
⒊对象与非对象间的相互联系
①友元(Friend)
遵循一定规则而使对象以外的软件系统能够不经过消息方式而直接访问对象内封装的成员数据的技术方法便是友元。
友元技术是C++语言所同时支持的面向对象系统与面向过程系统衔接的纽带;
⑴限定另一个类为本类的友元
格式:
friendclass友元类名;
这个声明使得该友元类的所有成员函数可直接访问本类中的全部成员(数据和函数)。
例19:
#include
usingnamespacestd;
classB;
classA
{
friendclassB;
inti;
public:
A(intx):
i(x){}
};
classB
{
intk;
public:
B(intx):
k(x){}
intadd(constA&x){returnk+=x.i;}
};
voidmain()
{
Aa
(1);
Bb
(1);
cout<}
⑵限定类中的一个成员函数为本类的友元
此种友元仅限制友元类中的某个(些)成员函数为本类的友元,与上一种友元比较,此种友元的存取范围要小的多。
其声明格式为:
friend返回类型类名∷成员函数名;
例20:
#include
usingnamespacestd;
classA;
classB
{
intk;
public:
B(intx):
k(x){}
intadd(constA&);
};
classA
{
friendintB:
:
add(constA&);
inti;
public:
A(intx):
i(x){}
};
intB:
:
add(constA&x)
{
returnk+=x.i;
}
voidmain()
{
Aa
(1);
Bb
(1);
cout<}
⑶限定一个全程函数为一个类的友元
这种友元是OO系统与过程系统的接口。
其格式为:
friend函数声明;
例21:
#include
usingnamespacestd;
classA
{
friendvoidmain();
inti;
public:
A(intx):
i(x){}
};
voidmain()
{
Aa
(1);
cout<<++a.i<}
2对象外的指针与对象
对象以外的指针在某些条件下是可以直接指向对象内的某个成员位置的。
这里的某些条件实际上就是使对象内的成员处在外界能够访问的位置上。
此种引用方式称为成员链接。
⑴成员数据的链接
C++语言成员数据链接的通用声明格式是:
类型符类名∷*指针名=类中成员地址描述;
C#语言则使用引用来实现成员数据的链接(但访问限制至少是internal以上的)。
例22:
C++语言示例
#include
usingnamespacestd;
classA
{
public:
inti,*p;
A(intx):
i(x){p=&i;}
};
intA:
:
*p=&A:
:
i;
voidmain()
{
Aa(10),b(10);
(b.*p)++;//括号不可缺,否则编译器将认为是句法错误
--*a.p;
cout<<”A:
”<”<cout<<”A:
”<<*a.p<<”B:
”<<*b.p<}
注意上例中“a.*p”和“*a.p”的区别。
例23:
C#语言示例
usingSystem;
classA
{
privateinti=0;
A(){}
staticvoidMain()
{
Ba=newB();
intp=a.i;//用引用类型直接链接
Console.WriteLine(p);
}
};
classB
{
internalinti=0;//注意
internalB(){}
};
⑵成员函数的链接
C++语言成员数据链接的通用声明格式是:
类型符(类名∷*指针名)(参数类型表)=&类名∷函数名;
C#语言的链接则通过委托技术来实现成员函数的链接的。
所谓委托就是将一个类(对象)成员函数指针的声明设定为一种新的委托成员数据类型,当需要建立链接时先占用内存储器空间建立对应的引用名,然后再定义该引用名与实际成员函数的链接关系。
C#语言中声明委托的语法为:
delegate委托函数指针名(参数表);
委托成员与类中其它成员一样,可以设置任何的附加类别的声明。
委托成员的声明必须与引用委托的成员函数放置在同一个类中。
C#语言还允许一个委托同时链接两个以上的(同类型)成员函数。
当发生调用该委托时,可逐个调用所链接的成员函数(未举例)。
例24:
C++语言示例
#include
usingnamespacestd;
classA
{
inti;
public:
intset(intk){i=++k;returni;}
};
voidmain()
{
int(A:
:
*f)(int)=&A:
:
set;
Aaa;
cout<<(aa.*f)(10);//括号不能省略
}
例25:
C#语言示例
usingSystem;
classA
{
delegateintFun(intx);//声明委托成员
stati