CC编程Tis.docx
《CC编程Tis.docx》由会员分享,可在线阅读,更多相关《CC编程Tis.docx(17页珍藏版)》请在冰点文库上搜索。
CC编程Tis
C/C++编程Tips
撰写人:
胡锋,刘春阳,马欢
2006.01
1.结构、联合和枚举名可直接作为类型名
在C++中,结构名、联合名、枚举名都是类型名。
在定义变量时,不必在结构名、联合名或枚举名前冠以struct、union、或enum。
2.无名联合
无名联合是C++中的一种特殊联合,它在关键字union后没有给出联合名,它可使一组数据项共享同一内存地址。
如:
union{intI;floatf;}在此无名联合中,声明了变量i和f具有相同的存储地址。
无名联合可通过使用其中的数据项直接存取。
如:
i=20;
3.C语言不同源文件间局部变量、静态局部变量、全局变量、静态全局变量、外部变量的作用域和生存周期
局部变量----生存周期:
从定义该变量的语句执行开始到所属作用域执行结束
作用域:
该变量所在的最近的花括号内
静态局部变量--生存周期:
从第一次调用该变量所在的函数开始,到程序结束
作用域:
该变量所在的最近的花括号内
全局变量----生存周期:
从程序开始到程序结束
作用域:
所有源文件内
外部变量--------:
其实是对全局变量的引用,跟全局变量代表同一个变量,
只不过在定义全局变量的源文件以外引用时,须用extern关键字声明一下,
在用extern关键字声明过的源文件中,该变量被称为"外部变量"
静态全局变量--生存周期:
从程序开始到程序结束
作用域:
静态全局变量所在的源文件内,不能被其他源文件引用
4.返回引用类型的函数与以引用类型作为参数的函数的用法
引用传递的性质像指针传递,而使用方式却像值传递。
返回引用的函数,一定要定义一个引用变量来接收函数的返回值。
5.有符号变量与无符号变量
有符号变量和无符号变量的区别在于,对同样的内容的解释不同,如对一个字节:
11111111,char型变量解释为-1,unsignedchar解释为255。
6.const加在函数后的意义
这种情况只出现在类的成员函数中,
加了const的成员函数相当于以只读方式打开文件那样“打开”了它所在的类。
即,后辍const的成员函数只能访问但不能修改所属类的成员变量。
并且,后辍const的成员函数也只能访问类中那些加了const的成员函数,普通的
成员函数无法访问。
7.typedef与结构体
经过typedef说明过的结构体,在定义变量的时候,可以直接使用结构体名作为类型,而不用再加上struct关键字。
内存对齐与位段。
8.extern与全局变量
如果全局变量要在其定义的源文件以外的源文件中使用,须用extern关键字加以声明,声明过后的这个全局变量,在当前的文件中就叫做外部变量。
9.const,inline,#define的区别
#define定义的常量不会经过编译器。
在预编译时,将用其定义的字符直接代替。
这种情况下,不做类型检查。
const 定义一个不可改变的变量。
编译器会给其分配内存空间,并且会在使用的地方做类型检查。
大多书中都推荐使用const而少用#define。
inline与以上两者没有关系。
定义成内联(inline)的函数在编译时,代码会被直接替换到调用该函数的地方。
10.new,delete;malloc和free的应用即区别
new/deleteversusmalloc/free
new/delete是操作符不是库函数,而malloc/free是。
new/delete可以完成任何类型的动态内存分配,自动识别并计算类型的内存大小。
malloc/free只是简单的分配一块大小的内存,更加底层。
最重要的在于,new/delete在分配用户自定义(特别是类)时,会自动调用类的构造/析构函数,完成类的初始化工作,malloc/free不具有此功能。
所以在分配C++内嵌类型(int等)和struct等简单对象时,malloc/free 与new/delete是等价的,但分配类对象时必须使用new/delete
11.switch的case语句内不能intm=4;但可以intm;m=4;
前一种写法,会报错:
errorC2360:
initializationof'm'isskippedby'case'label
12.头文件与预编译
一个头文件被包含多地,头文件里定义的变量或是类或函数会“重定义”。
为了避免这种情况,而使用预编译宏。
#ifdef/#define/#endif
13.浮点数的比较
由于浮点数有精度的问题,所以比较大小时没有问题,而在比较是否相等时,则不能像整数比较那些用“==”符号。
而应该将两者两减,当差值小于一个精度要求时,就认为两个浮点数相等。
14.声明与实现
static关键字或是函数的参数列表中的缺省值,都属于声明的范畴。
也就是说,应该写在头文件中。
如果在cpp文件中再写这些属于声明的范畴的关键字,就会报错:
重定义。
如,头文件a.h中:
classA{
privateI;
setI(inti=0);
}
在a.cpp文件中,
只要写
A:
setI(inti){
//实现
}
即可。
不能再写成:
A:
setI(inti=0){
//实现
}
15.链式操作
像strcpy()和operator=()等这些操作,返回值都会是目标的地址,这样做的目的是为了实现链式:
classA,B,C,D;A=B=C=D。
16.指针一旦定义出来,就要对它进行初始化,如果没什么值可赋,至少也要初始化为NULL,否则它指向一块未知的内存,就成为了野指针。
★17.构造函数与析构函数
每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝
构造函数,其它的称为普通构造函数)。
对于任意一个类A,如果不想编写上述函数,
C++编译器将自动为A产生四个缺省的函数,如
A(void);//缺省的无参数构造函数
A(constA&a);//缺省的拷贝构造函数
~A(void);//缺省的析构函数
A&operate=(constA&a);//缺省的赋值函数
注意:
①拷贝构造函数和赋值(构造)函数的区别:
拷贝构造意思是用一个类的实体初始化定义该类的另一个实体。
虽然也有“赋值”功能,但作用时机是该类另一个实体的定义阶段。
如:
Ab;Aa=b;这时,由于a是在定义时被“赋予”了b的内容,所以调用的是a的“
拷贝构造函数”而不是a的“赋值函数”。
再如:
Aa,b;a=b; 此时,由于“赋值”操作不是在a的定义阶段,所以执行的仅仅是一个普通“赋值”,故调用a的赋值函数而不是a的“拷贝构造函数”。
②“赋值函数”就是赋值函数,没有“赋值构造函数”这一说法。
很简单,因为“构造”的意义是指创建对象,而赋值是对象已经存在后的操作。
只是这个操作有点特别,即如果你不写,C++编译器会自动生成。
③自动生成的“赋值函数”是一个位拷贝函数。
什么意思?
即,如果你不自己写“赋值函数”,却使用以下的语名:
Aa,b;a=b; 那么b的内容将会被“位拷贝”到a中。
是的,你会说计算机中的数据本就是二进制(位)的,“位拷贝”会有什么错么?
没错。
当A类中没有定义指针变量时,确实不会出错。
但如果A中定义了指针变量,那么“位拷贝”后,a,b中的指针变量指针了同一个内存,后果有多严重可以想象了。
比如a的析构函数会释放其中指针所指空间,那么b在析构时会再次释放这片本已经释放的空间。
所以,此时一定要自己动手写“赋值函数”,从而复制指针所指向的内容,而不是复制指针变量本身的值。
④继承体系下构造函数的执行顺序,按照先父类后子类的顺序;多继承体系下构造函数的执行顺序,按照先左后右的顺序(classA:
publicB,publicC;则先构造B,再构造C,再构造A);类中还有类时,构造顺序按定义顺序。
如classA{classB;classC;classD};那么构造顺序是B,C,D,A。
即,一个类的所有资源(基类、成员)都构造完毕后,这个类本身的构造函数才会执行。
析构顺序正好相反,先析构自身,再析构自身的成员,再析构基类。
⑤特别注意“赋值函数”的写法。
例:
classString{
char*ptr;
String&operator=(constString&);
}
String&String:
:
operator=(constString&src){
if(this==&src)return*this;//防止自我赋值
deleteptr;//释放原区域
ptr=newchar[strlen(src.ptr)+1];//分配新区域
strcpy(ptr,src.ptr);//赋值
return*this;//请注意返回的是自身
}
应用:
Stringa,b;
a=b;
a.operator(b);
以上两种写法等价。
注意一点,由于参数是const型,所以无论是自己写的赋值函数还是自动生成的赋值函数,返回的都是左值的引用。
自己写的赋值函数中,调用的是a对象的赋值函数,而返回值仍然是a的引用。
如果“赋值函数”中没有“释放”、“分配”、“赋值”操作,这个函数什么也没做。
对于自动生成的赋值函数,检测其返回左值引用还是右值引用的方法:
由于(a=b)返回的是a的引用,故最后是a被赋了c的值,但b的值没有改变。
★18.传值,传址,传引用的区别
传值,是将对象(类实体或普通类型实体)当作参数传递,取参数时,也是取其值。
程序运行时,会在栈空间上新建变量来存储这些值,存在值的复制。
传址,是将对象的地址当作参数传递。
取参数时,也是取其地址。
程序运行时会在栈空间上新建一个指针变量来存储对象的地址,不存在值的复制问题。
传引用,是将对象的当作参数传递。
(请特别注意,是将对象而不是地址当作参数传递。
)但是,取值时,取对象的地址。
程序运行时会在栈空间上新建一个指针变量来存储这个地址。
不过与传址不同的时,引用虽然实际取的是对象地址(相当于指针),但你可以将其按对象来访问。
如:
CStringa;CString&ra=a;CString*pa=&a;执行ra.GetLength()和pa->GetLength()都可以得到a的长度。
不同在于ra是按对象来访问,pa是按指针来访问。
而其实际操作对象都是a。
19.static关键字
⑴静态成员变量/函数
静态成员在类中声明,在声明时并不为它分配内存空间,使用前必须在类外定义一下,为其分配内存空间。
父类的静态成员被子类继承后,子类如没有重新声明同名的成员,子类的对象和父类的对象共用同一个静态变量。
如果子类重新声明了同名的成员,则子类的声明的成员跟父类声明的成员同时共存,但引用时,父类的成员须使用作用域操作符显式引用。
(显示引用的位置取好在类所对应的cpp文件中)
⑵静态变量
静态变量用static来修饰,静态变量与程序的内存分配有关。
内存分配方式有三种:
(1)从静态存储区域分配。
内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。
例如全局变量,static变量。
(2)在栈上创建。
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函
数执行结束时这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配。
程序在运行的时候用malloc或new申请任意多
少的内存,程序员自己负责在何时用free或delete释放内存。
动态内存的生存期
由我们决定,使用非常灵活,但问题也最多。
20.指针的强制转换
指针的强制转换可与多态联系起来。
一个对象实体可以被赋值给另一种对象类型的指针。
需要处理的是,如果是动态绑定,则根据对象实体来确定访问类型;如果是静态绑定,则根据对象被给的指针类型来确定访问类型。
这种情况与多态联系了起来。
此外,任何指针都是32位,任何一种类型的指针都可以转换为void*,当然,类的指针也不例外。
不同的是,不同类型的指针加减法操作产生的地址偏移量各不相同。
char型指针增1偏移量是1字节,short型指针是2字节,long型指针是4字节,指针的指针是4字节,类的指针也是4字节(注意,对类的指针进行增1操作是没有任何意义的,勿用)。
21.友元
友元一种特殊的函数,它本身不是类的成元函数,但它可以象类的成元函数一样访问类的所有成元变量。
友元声明在类中使用friend关键字,它可以是一个普通函数,也可以是其它类的成元函数,其定义和普通函数一样。
由于友元的存在破坏了类的封闭性,所以应尽量减少使用。
22.使用拷贝构造函数需要注意的问题
使用拷贝构造函数时,如果对象的成员里含有指针,就不能直接赋值,而要为指针重新分配空间,使不同对象的指针指向不同的内存。
否则可能引起混乱。
23.public,protected,private继承。
public继承:
父类的public变量在子类中还是public变量
父类的protected变量在子类中还是protected变量
父类的private变量在子类中还是private变量
protected继承:
父类的public变量在子类中变成protected变量
父类的protected变量在子类中还是protected变量
父类的private变量在子类中还是private变量
private继承:
父类的public变量在子类中变成private变量
父类的protected变量在子类中变成private变量
父类的private变量在子类中还是private变量
24.虚基类
虚基类就是在继承的时候用virtual关键字说明继承的类,如果继承有多个分支,则凡是用virtual说明的同一个类,在继承时就合并为同一个拷贝。
★25.决不要重新定义继承而来的缺省参数值
因为,虚函数是动态绑定而缺省参数值是静态绑定的。
如果基类A的虚函数有一个缺省参数值,子类B对应的虚函数定义了另一个缺省参数值,那么,对于A*p=newB这种情况,由于虚函数是动态绑定而缺省参数值是静态绑定,所以执行p->虚函数(),会执行B类的函数,但由于指针类型是A,所以缺省参数值使用的是A的虚函数中定义的缺省参数值。
这种混和用法显然不是你想要的。
26.当心隐式类型转换导致重载函数产生二义性
例如:
voidfun(inti);voidfun(floatf);当调用fun(0.5);的时候由于0.5是double型常量,编译器将不知道是把它转换为int型还是float型,就会报错。
★27.令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。
此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。
此时,基类的函数被隐藏(注意别与覆盖混淆)。
★当基类函数被隐藏时,必须显示地调用基类函数,否则调用不到。
28.函数重载
函数的重载就是使用同样的名子,但实现了不同的功能的一组函数。
这一组函数分为编译时的静态重型与运行时的动态重载。
29.多态,重构,重写
多态的概念是类的虚函数的作用来体现的。
一个类的对象可能会被赋值给不同的地址类型,虚函数可以保证按对象的实际类型进行访问,而不是根据指针类型来进行访问。
重构,很简单,两个函数名相同,参数列表不同的函数,在同一个作用域下称为重构。
而对于重写,是指子类中定义了与父类函数名相同,参数列表也相同函数。
此时,可参考同名的全局变量和局部变量的作用域来理解子类中会调用哪个函数。
★30.决不要重新定义继承而来的缺省参数值
因为,虚函数是动态绑定而缺省参数值是静态绑定的。
如果基类A的虚函数有一个缺省参数值,子类B对应的虚函数定义了另一个缺省参数值,那么,对于A*p=newB这种情况,由于虚函数是动态绑定而缺省参数值是静态绑定,所以执行p->虚函数(),会执行B类的函数,但由于指针类型是A,所以缺省参数值使用的是A的虚函数中定义的缺省参数值。
这种混和用法显然不是你想要的。
31.潜在的二义性
函数定义中,参数列表可以定义缺省值。
与此同时,同名函数也可以根据参数列表中参数个数不同而有不同的版本(重构)。
所以,需要注意一种情况,即缺省后的参数列表个数与重构的版本的参数列表个数相同,这会产生二义性。
如:
①voida(intx=1,inty=2){
//…
}
②voida(intx){
//…
}
③voida(){
//…
}
当你调用 a(5,6)没有问题。
可是调用a(6)和a()就会产生二义性了。
编译器无法知道a(6)是①(y使用缺省值)还是②(只有一个参数)。
对于a(),也是类似的情况。
32.重载运算符
在C++中可以通过对运算符的重载而实现简化操作。
运算符重载分为:
类以外的运算符重载,成员运算符重载,友元运算符重载。
它们名称不同,但都使用统一的定义格式:
typeoperator@(parameter-list)
type为返回类型
parameter-list为参数列表
@为要重载的操作符.
注意:
重载运算符与预定义运算符的使用方法完全相同。
它不能改变原有运算符的参数个数也不能改变原有的优先级和结合性。
33.成员运算符函数与友元运算符函数的比较:
1) 对于参数个数来说,成员总比友元少一个。
2) 双目运算符可以重载为成员或是友元,但如果这个运算符本身是不交换律的,那只能重载为友元运算符。
3)++与――的重载
++与――在操作数的前面或后面是有区别的,而operator@()无法区分是前缀还是后缀方式。
现在使用一个int参数来区分它们
前缀方式++obj
Operator++()
后缀方式obj++
Operator++(int)
使用和变通的一样。
34.链式操作
像strcpy()和operator=()等这些操作,返回值都会是目标的地址,这样做的目的是为了实现链式:
classA,B,C,D;A=B=C=D。
35.纯虚函数
对虚函数只声明,不定义,就叫做纯虚函数,含有纯虚函数的类叫做抽象类,不能创建抽象类的对象。
抽象类主要是用来继承的,如果子类没有实现抽象类的纯虚函数,那么子类也是一个抽象类。
如果想让子类是一个非抽象类,则子类必须实现继承的纯虚函数。
纯虚函数的声明是在虚函数的声明后加上“=0”,如:
virtualintfunc(inti)=0;
36.模板定义
函数模板提供一个种用来自动生成各种类型函数实例的算法。
程序员对于函数接口参数和返回类型中的全部或者部分类型进行参数化parameterize而函数体保持不变。
假设有一求最小值的函数min,其模板定义如下:
Template
TYPEmin(TYPEa,TYPEb){
Returna>b?
b:
a;
}
关键字template总是放在模板的定义与声明的最前面关键字后面是用逗号分隔的模板参数表templateparameterlist它用尖括号<>一个小于号和一个大于号括起来。
TYPE称为模板类型参数,是参数化的变量类型。
注意:
对于模板中的每一个类型参数声明,前面都必须要加上class或是typename关键字。
37.模板的使用
模板函数的实现过程和普通函数一样,只是其中的参数的类型就了类型参数,是一个可变的变量,在不同的实例化过程中代表不同的实际类型。
模板函数也可以被声明成为类的inline或是extern。
不过这两个关键字必须加在模板参数列表后面,函数实体前面。
即
Template
Inline
TYPEmin(TYPEa,TYPEb){
Returna
a:
b;
}
38.模板类
模板类与模板函数在实现原理上是一致的。
其定义规则如下:
template
classclass_name{
//类的定义实体
}
注意:
如果要在类体外定义类的成员函数而这个函数中有使用到模板类型参数,那就要象定义模板函数一样,声明模板函数。
Template
TYPEclass:
:
func_name(TYPEa){
//函数体定义
}
39.C++类库对模板的支持
在C++中定义了很多的模板类与模板函数。
特别是一些容器类型如:
vector(数组)list(链表)map(映射表)set(集合)等,都大量使用了模板的方法。
下面一实例展示如何使用它们:
#include
#include
usingnamespacestd;
intmain(void){
vectorvstr;//定义一个存储字符串的向量容器
cout<<"vstrcapacity:
"<cout<<"vstrsize:
"<vstr.push_back("firststr");//加入新元素到容器
vstr.push_back("secondstr");
cout<<"afterputtwostringintovector"<cout<<"vstrcapacity:
"<cout<<"vstrsize:
"<vector:
:
iteratoriter=vstr.begin();//取得容器的第一个元素的地址
cout<<"1strinvstr:
\t"<<*iter<cout<<"2strinvstr:
\t"<<*(++iter);
}
40.使用MFC类模板的例子
运行结果:
41.关于传参(char**)和(char*)的问题
如果想在一个函数内改变一个实参的值,并且这种改变在函数调用完成后仍然存在,那么就应该传给函数该实参的地址,即指向该实参的指针。
如果实参是一个指针,而函数想改变该指针保存的地址,就应该传给函数该指针的地址,即指向该指针的指针。
42.chara[2][4]={"abc","def"};char**p=a;这里用*p并不能得到一个字符串,因为a并不指向一个指针。
另外,这里写p++也没有意义,除非它指向一个指针数组;应该用char*p[4]来指向它
注:
chara[3][4];char**p=a,这种用法只有C语言编译器支持,C++编译器不支持。
(即只