C必知必会.docx
《C必知必会.docx》由会员分享,可在线阅读,更多相关《C必知必会.docx(29页珍藏版)》请在冰点文库上搜索。
![C必知必会.docx](https://file1.bingdoc.com/fileroot1/2023-5/8/2bae9617-2164-4c25-9122-9daac9b4e2ad/2bae9617-2164-4c25-9122-9daac9b4e2ad1.gif)
C必知必会
1引用是别名而非指针
引用和指针的三大区别:
1不存在空引用(nullreference)
2所以引用都要初始化
3一个引用永远指向用来对它初始化的那个对象。
Employee&anEmployee=*static_cast(0);
//试图创建一个空引用是错误的。
Template
Voidswap(T&a,T&b)
{
Ttemp(a);
a=b;
b=temp;
}
Intgrades[MAX]
Swap(grades[i],grades[j]);
//引用可以指向的对象没有名字,注意只是没有名字,是变量,非常量。
Inlinevoidset_2d(float*a,intm,inti,intj)
{
Float&r=a[i*m+j];//引用的对象没有名字而已。
r=r*r+r;
}
常量对象的引用:
Constdouble&cd=12.3;
Template
Tadd(constT&a,T&b)
{
returna+b;//返回的是一个T类型的对象或值。
}
conststring&greeting=add(“hello”+”,world!
”);;
//注意“hello”为常量的时候,其函数参数必须为constT&常量引用类型。
2数组形参是指向其首元素的指针(T*)
1一维数组形参:
Voidaverage(intary[12]);//形参ary实际上就是int*传入4个字节的地址。
当然函数形参也可以指定接受多少维的数组,如:
Voidaverage(int(&ary)[12]);//只能接受12个整形数的一维数组。
传统的做法(泛型):
Template
Voidaverage(int(&ary)[n])
{
Average_n(ary[],n);
}
int(&ary)[n]这里只能接受一维数组,如intnum[]={1,2,3,4,5};
不能接受int*的一个地址。
会产生类型不匹配的错误。
2多维数组形参
Voidprocess(intary[10][20]);
//指向数组首元素的指针。
其实ary[10]是地址。
Voidprocess(int(*ary)[20]);
Int*ary=一维数组的首地址一样。
传统的用法:
Template
Inlinevoidprocess(int(&ary)[n][m])
{
Process_2d(&ary[0][0],n,m);
}
3常量指针和指向常量的指针
1指向常量的指针:
constinta=12;constint*cp=&a;
不能用(*cp)++;试图改变a内存中的值。
指向的对象不能改变。
指向常量
在形参中比较常用。
保证不修改传进来的变量的值。
2常量指针:
int*constcp=&a;//cp不能再指向其他的内存地址了。
指针不能改变。
指向非常量。
4指向指针的指针
1函数参数为指向指针的指针
char*sendto(char**p,charc)
{
while(**p&&**p!
=c)
++*p;
printf("%s\n",*p);
return*p;
}
chars[]="hello,world";
char*cp=s;//指向指针的指针。
cp=sendto(&cp,',');
printf("%s\n",cp);
2函数参数为指向指针的引用:
char*scanto(char*&p,charc)
{
while(*p&&*p!
=c)
++p;
printf("%s\n",p);
returnp;
}
chars[]="hello,world";
char*cp=s;//指向指针的引用。
char*&p=cp;其实p就是s。
scanto(cp,',');
printf("%s\n",cp);
注意指向指针的指针适用于基本类型,不适用于对象。
如:
Circlec=newCircle();
Circle**cc=&c;
Shape**ss=cc;//指向是错误的。
只能是基类的指针指向派生类的对象,只有一级指针的指向。
否则无法解释。
1将非常量的指针转换为常量的指针,可以:
Char*s1=0;//非常量的指针
Constchar*s2=s1//将非常量的指针转换为常量的指针。
但是将非常量的指针的指针转换为指向常量的指针的指针就不可以,
如
Char*a[max]=0;//非常量的指针的指针a。
Constchar**p2=a//将非常量的指针的指针a转换为常量的指针的指针。
这样是不可以的。
5C++形式转换操作符
Typedefchar*pchar;
Char*hopstAddr=pchar(0x00ff0000)
//旧的可以讲常量的值转换为可访问的地址值。
这是非常危险的事。
1Const_cast操作符:
允许添加和移除表达式中类型的const或volatile修饰符。
Constperson*getEmployee(){…..};
Person*anEmployee=const_cast(getEmployee());
//这里移除Const修饰符。
变更为person*类型。
用老版本的也可以实现:
Person*anEmployee=(person*)getEmployee();
强制类型转换。
2static_cast操作符:
跨平台移植的转型。
Shape*sp=newcircle;
Circle*cp=static_case(sp);
向下转型:
从基类转向派生类的对象的指针。
Circle*cp=sp;不能直接使用,会有语法错误,必须使用static_case转换。
3static_cast和const_cast混合使用。
Constshape*getNextShape(){….}//基类中的函数,级别高
Circle*cp=static_cast(const_case(getNextShape()));
4reinterpret_cast从bit看待一个对象,将一个东西看作另一个不相关的东西。
(不可移植。
)
如:
char*hopeitwork=reinterpret_cast(0x00ff0000);
//将一个整形的数据看成不相关的字符指针的地址。
int*hopeless=reinterpret_cast(hopeitwork);
//将char*类型的东西看作int*类型的东西。
5dynamic_cast跟static_cast差不多,但dynamic_cast从基类向下转型时,基类必须有虚函数,在运行时检查。
如:
If(constcircle*cp=dynamic_cast(getNextShape()))
{
//如果基类有虚函数(getNextShape)可以转换,则转换成功,否则转换为空。
}
Constcircle&rc=dynamic_cast(*getNextShape());
//引用类型向下转换。
6常量成员函数
常量成员函数,不能修改类对象的内部数据。
Classx{
Privatex;
IntgetValue()const;
}
IntgetValue()const
{
X=x+100;//不能对常量函数进行对象数据的修改。
//当然若要修改,可以用const_cast去掉成员的const属性,但这样做失去意义。
//但在对象内部数据若修饰为mutable时,就可以在常量成员函数中修改对象中修饰为mutable的内部数据。
}
类对象的this指针类型加上常量修饰符。
一般在重载操作符中体现:
Classx{
Public:
Int&operator[](intindex);//重载操作符[],没有常量修饰符。
Constint&operator[](intindex)const;//重载操作符[],有常量修饰符
}
使用:
IntI=12;
Xa;
A[1]=I;//调用非常量修饰符的重载操作符
ConstXb;
I=b[1];//调用重载的常量修饰符的重载操作符。
又如重载左值得例子:
Xoperator+(constx&,constx&);//普通的,非类的成员函数。
Xoperator+(constx&rightArg);//普通的,类的成员函数,带左值:
非常量。
Xoperator+(constx&rightArg)const;//普通的,类的成员函数,带左值:
常量。
7类对象的复制:
类对象的复制,不能使用memcpy来复制,因为,若类中又虚类指针时,编译器可能将虚指针的地址放在开头处,会覆盖虚表指针的地址。
8赋值与初始化
explicit用来防止由构造函数定义的隐式转换。
Classstring{
Public:
FriendConststringoperator+(conststring&a,conststring&b);
Private:
String(constchar&str,constchar&str2);
//私有构造函数,也称计算性构造函数
}
conststringoperator+(conststring&a,conststring&b)
{
returnstring(a,b);//调用内部私有类构造。
}
9复制操作:
复制构造和复制赋值操作符
Classimpl;
Classhandl{
Public:
Handle{consthandl&};//复制构造函数。
Handle&operator=(consthandle&)//赋值=操作符重载。
Private:
Impl*impl_;//指向句柄的实现。
}
复制构造函数的实现:
handleh1=newhandle();
handleh2(h1);//调用复制构造。
Handleh2=h1;//调用复制赋值函数。
类内部的只有一个数据的简单的交换操作:
Inlinevoidhandle:
:
swap(handle&that)
{
Std:
:
swap(impl_,that.impl_);
}
Handle&Handle:
:
operator=(consthandle&that)//赋值=操作符重载。
{
Handletemp(that);//复制构造
Swap(temp);//内部数据指针交换
Return*this;
}
10函数指针:
void(*fp)(函数参数);
externvoidfunDOG();
externvoidfunCAT();
void(*fp)();//定义函数指针,注意定义的函数指针的类型和其指向的函数类型必须相同。
fp=0;//可以指向NULL。
fp=funDOG;
fp=&funCAT;明确赋给的是函数的地址。
fp();//隐式的使用
(*fp)();
函数指针的使用案例:
定义两个函数:
VoidstopDropRoll();
VoidjumpIn();
定义函数指针:
Void(*fp)()=0;
if(!
fatalist)
{
fp=jumpIn
else
fp=stopDropRoll;
}
函数指针的回调:
不带参数且返回void类型。
Typedefvoid(*new_handler)();//定义无参的返回void类型的函数指针。
Std:
:
new_handleroldhandle=std:
:
set_new_handler(函数地址或函数名);
11指向类成员的指针但并非指针
Int*ip;//普通指针
Int*类名:
:
cp;//指针指向类的一个int的成员。
格式:
1. <数据类型><类名>:
:
*<指针名>[=&<类名>:
:
<非静态数据成员>]
指向非静态数据成员的指针在定义时必须和类相关联,在使用时必须和具体的对象关联。
【示例13-14】 假设已经定义了一个类student,该类有非静态成员math,静态成员chin-ese,代码演示了指向它们的指针定义方式。
1. student s1;
2. int student:
:
*pm=&student:
:
math; //指向非静态属性
3. s1.*pm=100; //设置非静态属性
4. int *pc=&student:
:
chinese; //指向静态属性
5. *pc=10; //设置静态属性
分析:
该示例定义了指针pc和pm,分别指向类的静态数据成员chinese和非静态数据成员math。
访问pm时,必须使用类实例来修饰。
而访问pc时,与普通指针没有区别。
基类和派生类的数据成员的指针的用法:
Pointcircle:
:
*loc=&shape:
:
center_;
基类(shape)中的数据向派生类(circle)转换可以,反之不可以。
//说明数据是pointcenter_类型的。
Doubleshape:
:
*extent=&circle:
:
radius_
派生类中的数据向基类转换,不可以。
基类中都没有派生类的数据,如何转换。
2.指向成员函数的指针
定义一个指向非静态成员函数的指针必须在三个方面与其指向的成员函数保持一致:
参数列表要相同、返回类型要相同、所属的类型要相同。
定义格式如下:
1. <数据类型>(<类名>:
:
*<指针名>)(<参数列表>)[const][=&<类名>:
:
<非静态成员函数>]
使用指向非静态成员函数的指针的方法和使用指向非静态数据成员的指针的方法相同,格式如下:
1. (<类对象名>.*<指向非静态成员函数的指针>)(<参数列表>);
指向静态成员函数的指针和普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联。
classshape{
public:
voidmoveTo();
boolvalidate();
virtualbooldraw()const;
};
classcircle:
publicshape
{
booldraw()const;
};
void(shape:
:
*moveTo)()=&shape:
:
moveTo;
circlecr;
shape*sp=&cr;//采用基类指针指向派生类对象。
(sp->*moveTo)();//(cr.*moveTo)()或cr.moveTo();
bool(shape:
:
*p_draw)()=&shape:
:
draw;
(sp->*p_draw)();//cr.draw();虚函数执行的是指向派生类的函数。
1. <数据类型>(*<指针名>)(<参数列表>)[=&<类名>:
:
<静态成员函数>]
【示例13-15】 假设类student有非静态成员函数f1,非静态成员函数f2,代码演示指向它们的函数指针的定义方式。
1. student s1;
2. float (student:
:
*pf1)()=&student:
:
f1; //指向非静态成员函数的指针
3. (s1.*pf1)(); //调用指向非静态成员函数的指针
4. void (*pf2)()=&student:
:
f2; //指向静态成员函数的指针
5. pf2(); //调用静态成员函数
分析:
指向非静态成员函数时,必须用类名作限定符,使用时则必须用类的实例作限定符。
指向静态成员函数时,则不需要使用类名作限定符。
12处理函数:
函数指针和函数引用
Intshow();
Int(*sw)()=show;//定义函数指针
Int(&sw)()=show;//定义函数的引用。
Int(const*sw)()=show;
//定义常量函数指针。
即该函数指针不能再指向其他函数。
Int(*afp3[n])();
首先是数组,存的是地址,是函数的地址
拆开,等同于下:
Typedefint(*fp)();预定义的函数指针
Fpafp3[n];数组,存的是函数指针。
13函数对象的使用:
经典Fib()
classFib{
public:
Fib():
a0_
(1),a1_
(1){}
intoperator()();
//重载函数对象,注意不要丢括号,前一个是重载(),后一个是函数(可带参数)
private:
inta0_,a1_;
};
intFib:
:
operator()()
{
inttemp=a0_;
a0_=a1_;
a1_=temp+a0_;
returntemp;
}
使用方法:
Fibfib;
cout<//注意这里使用的是“对象+()”==fib.operator()()
Fibfib;
for(inti=0;i<20;i++)
{
if(i<19)
{
cout<}
else
{
cout<}
}
用函数指针类实现接口的典型例子:
typedefdouble(*Fun)(double);
//预定义函数指针
doubleintegrate(Funf,doublelow,doublehigh)
{
//求积分的函数,用函数指针来实现,传入函数指针f。
constintnumsteps=8;
doublestep=(high-low)/numsteps;
doublearea=0.0;
while(low{
area+=f(low)*step;
//这里使用函数指针
low+=step;
}
returnarea;
}
doubleFunInterSquare(doubled)
{
//实现接口的函数,正方形面积
returnd*d;
}
doubleFunInterCircle(doubled)
{
//实现接口的函数,正方形面积
return3.14*d*d;
}
实现:
doublearea=integrate(FunInterCircle,0.0,,4.0);
采用类简单接口类,声明一个纯虚operator()(double),派生类实现该接口,并指向所需要的函数。
classFUNC
{
public:
virtual~FUNC(){}
virtualdoubleoperator()(double)=0;
//采用虚基类的方式
};
//子类实现基类的方法:
typedefdouble(*FUN)(double);
classNMFUNC:
publicFUNC
{
public:
NMFUNC(FUNf):
f_(f){}
doubleoperator()(doubled){returnf_(d);}
private:
FUNf_;
//定义函数指针,在构造时传入
};
doubleFunInterSquare(doubled)
{
//实现接口的函数,正方形面积
returnd*d;
}
doubleintegrate(FUNC&f,doublelow,doublehigh)
{
//求积分的函数,用函数指针来实现,传入函数指针f。
constintnumsteps=8;
doublestep=(high-low)/numsteps;
doublearea=0.0;
while(low{
area+=f(low)*step;
//这里使用对象的()括号的重载操作符。
low+=step;
}
returnarea;
}
实现:
NMFUNCg(FunInterSquare);
doublearea=integrate(g,0.0,2.7);
采用模板类:
14按钮的command模式:
函数的回调模式
客户和服务框架分开:
classButton{
public:
Button(conststring&label):
label_(label),action_(0){}
voidsetAction(void(*newAction)())
{
//设置动作的函数指针
action_=newAction;
}
voidonClick()const
{
if(action_)
{
action_();
//执行动作
}
}
private:
stringlabel_;
void(*action_)();//这里太死板,不灵活。
//定义了按钮的字符串和动作函数指针。
};
使用的时候设置回调函数:
1设置需回调的函数:
voidplayMusic()
{
printf("回调:
播放音乐!
");
}
2使用这个回调:
Button*b=newButton("客户");
b->setAction(playMusic);
//执行
b->onClick();
通过专门的动作类对象指向的成