c++学习笔记.docx
- 文档编号:14778840
- 上传时间:2023-06-27
- 格式:DOCX
- 页数:43
- 大小:442.95KB
c++学习笔记.docx
《c++学习笔记.docx》由会员分享,可在线阅读,更多相关《c++学习笔记.docx(43页珍藏版)》请在冰点文库上搜索。
c++学习笔记
函数对象
1.函数对象的定义
//声明一个普通的类并重载“()”操作符:
classNegate
{
public:
intoperator()(intn){return-n;}
};
重载操作语句中,记住第一个圆括弧总是空的,因为它代表重载的操作符名;第二个圆括弧是参数列表。
一般在重载操作符时,参数数量是固定的,而重载“()”操作符时有所不同,它可以有任意多个参数。
2.使用函数对象
#include
usingstd:
:
cout;
voidCallback(intn,Negate&neg)
{
intval=neg(n);//调用重载的操作符“()”
cout< } //注意neg是对象,而不是函数。 编译时,编译器将语句 intval=neg(n);转化为intval=neg.operator()(n); 通常,函数对象不定义构造函数和析构函数。 因此,在创建和销毁过程中就不会发生任何问题。 前面曾提到过,编译器能内联重载的操作符代码,所以就避免了与函数调用相关的运行时问题。 //为了完成上面个例子,我们用主函数main()实现Callback()的参数传递: intmain() { Callback(5,Negate());//输出-5 } 本例传递整数5和一个临时Negate对象到Callback(),然后程序输出-5。 3.编写模板函数对象 从上面的例子中可以看出,其数据类型被限制在int,而通用性是函数对象的优势之一,如何创建具有通用性的函数对象呢? 方法是使用模板,也就是将重载的操作符“()”定义为类成员模板,以便函数对象适用于任何数据类型: 如double,_int64 classGenericNegate { public: template }; intmain() { GenericNegatenegate; cout< cout< } 4.标准库中函数对象 C++标准库定义了几个有用的函数对象,它们可以被放到STL算法中。 例如,sort()算法以 判断对象(predicateobject)作为其第三个参数。 判断对象是一个返回Boolean型结果的 模板化的函数对象。 可以向sort()传递greater<>或者less<>来强行实现排序的升序或降序: #include #include 函数对象用于求最大值 #include #include #include usingnamespacestd; template constObject&findMax(constvector { intmaxIndex=0; for(inti=1;i if(cmp.isLessThan(arr[maxIndex],arr[i]))//此时的isLessThan还不知道具体的定义,要看cmp到底是什么 maxIndex=i;//maxIndex每次取到的都是i值,而且只有当arr[maxIndex] returnarr[maxIndex]; } classCaseInsensitiveCompare { public: //true: lhs lhs>rhs boolisLessThan(conststring&lhs,conststring&rhs)const { return_stricmp(lhs.c_str(),rhs.c_str())<0;//使用string的成员函数,不区分大小写 //returnlhs } }; intmain() { vector arr[0]="ZEBRA"; arr[1]="zebrc"; arr[2]="zebrf"; arr[3]="zebrd"; cout< return0; } 虚函数表 基类的析构函数为什么要设为Virtual 未初始化的对象指针调用虚函数时会导致错误 Father*pf=NULL;//未初始化的对象指针调用虚函数时会导致错误 pf->vir();//运行错误 虚函数实现多态 #include usingnamespacestd; classFather { public: virtualvoidvir() { cout<<"父类的vir()函数被执行"< } }; classSon: publicFather { public: voidvir() { cout<<"子类的vir()函数被执行"< } }; intmain() { Fatherf; Sons; f.vir();//父类的vir()函数被执行 s.vir();//子类的vir()函数被执行 Father*pf; pf=newFather;//父类对象赋值给父类指针,也可以通过&f赋值 pf->vir();//父类的vir()函数被执行 pf=newSon;//子类对象赋值给父类指针,也可以通过&s赋值 pf->vir();//子类的vir()函数被执行 return0; } 1.子类对象可以赋值给父类指针(可以通过取地址或者new赋值) 2.父类对象不可以赋值给子类指针,因为子类指针很可能访问到父类没有的一些属性及函数,会出错 3.对于纯虚基类,不能创建对象,也不能使用new,只能创建指针,但只能将子类对象赋值给这个基类指针;因而纯虚基类的指针也只能访问子类的同名虚函数。 C++中不能重载为友元函数的四个运算符 C++规定有四个运算符=,->,[],()不能重载为友员函数,只能重载为类的非静态成员函数 初始化和赋值的区别 拷贝构造函数和赋值构造函数 1.初始化: 对于一个类Aa对象,初始化发生在定义a时,比如Aa(7)或Aa=7;都叫初始化,都是调用自己定义的构造函数 2.赋值: 给已经存在了的对象赋值,如Aa;a=7; 3.拷贝构造函数首先是一个构造函数,同样没有返回值,也是用来构造一个对象的,只不过参数是一个类对象,即用一个对象构造一个新对象 4.赋值构造函数是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。 而且需要返回一个对象自身的引用(这必定要调用拷贝构造函数),以便赋值之后的操作 下面分两种情况讨论一段代码 a.重载了赋值运算符 #include usingnamespacestd; classA { public: A() { x=99;cout<<"CallA()"< } A(intxx) { cout<<"CallA(intxx)"< x=xx; } A(A&a) { x=a.x; cout<<"拷贝构造函数被调用"< } Aoperator=(Aa)//赋值构造函数被调用 { x=a.x; cout<<"赋值构造函数被调用"< return*this; } Aoperator=(intxx)//重载赋值运算符 { cout<<"重载赋值运算符被调用"< x=xx; return*this; } private: intx; }; intmain() { Aa;//调用默认构造函数 a=7;//由于重载了上述赋值运算符,故调用赋值运算符,在赋值运算符内又调用了拷贝构造函数 Ab(a);//调用拷贝构造函数 system("pause"); return0; } 输出结果: b.没有重载赋值运算符 #include usingnamespacestd; classA { public: A() { x=99;cout<<"CallA()"< } A(intxx) { cout<<"CallA(intxx)"< x=xx; } A(A&a) { x=a.x; cout<<"拷贝构造函数被调用"< } Aoperator=(Aa)//赋值构造函数被调用 { x=a.x; cout<<"赋值构造函数被调用"< return*this; } private: intx; }; intmain() { Aa;//调用默认构造函数 a=7;//由于没有重载了上述赋值运算符,故先调用构造函数创建一个临时对象,再调用赋值构造函数,在赋值构造函数内又调用了拷贝构造函数 Ab(a);//调用拷贝构造函数 system("pause"); return0; } 输出结果: c++中#include using: : abs;//绝对值 using: : acos;//反余弦 using: : acosf;//反余弦 using: : acosl;//反余弦 using: : asin;//反正弦 using: : asinf;//反正弦 using: : asinl;//反正弦 using: : atan;//反正切 using: : atan2;//y/x的反正切 using: : atan2f;//y/x的反正切 using: : atan2l;//y/x的反正切 using: : atanf;//反正切 using: : atanl;//反正切 using: : ceil;//上取整 using: : ceilf;//上取整 using: : ceill;//上取整 using: : cos;//余弦 using: : cosf;//余弦 using: : cosh;//双曲余弦 using: : coshf;//双曲余弦 using: : coshl;//双曲余弦 using: : cosl;//余弦 using: : exp;//指数值 using: : expf;//指数值 using: : expl;//指数值 using: : fabs;//绝对值 using: : fabsf;//绝对值 using: : fabsl;//绝对值 using: : floor;//下取整 using: : floorf;//下取整 using: : floorl;//下取整 using: : fmod;//求余 using: : fmodf;//求余 using: : fmodl;//求余 using: : frexp;//返回value=x*2n中x的值,n存贮在eptr中 using: : frexpf;//返回value=x*2n中x的值,n存贮在eptr中 using: : frexpl;//返回value=x*2n中x的值,n存贮在eptr中 using: : ldexp;//返回value*2exp的值 using: : ldexpf;//返回value*2exp的值 using: : ldexpl;//返回value*2exp的值 using: : log;//对数 using: : log10;//对数 using: : log10f;//对数 using: : log10l;//对数 using: : logf;//对数 using: : logl;//对数 using: : modf;//将双精度数value分解成尾数和阶 using: : modff;//将双精度数value分解成尾数和阶 using: : modfl;//将双精度数value分解成尾数和阶 using: : pow;//计算幂 using: : powf;//计算幂 using: : powl;//计算幂 using: : sin;//正弦 using: : sinf;//正弦 using: : sinh;//双曲正弦 using: : sinhf;//双曲正弦 using: : sinhl;//双曲正弦 using: : sinl;//正弦 using: : sqrt;//开方 using: : sqrtf;//开方 using: : sqrtl;//开方 using: : tan;//正切 using: : tanf;//正切 using: : tanh;//双曲正切 using: : tanhf;//双曲正切 using: : tanhl;//双曲正切 using: : tanl;//正切 intabs(inti);//处理int类型的取绝对值 doublefabs(doublei);//处理double类型的取绝对值 floatfabsf(floati);/处理float类型的取绝对值 C++运算符重载 1.怎么实现运算符的重载? 方式: 类的成员函数或友元函数(类外的普通函数) 规则: 不能重载的运算符有. 和.*和? : 和: : 和sizeof 友元函数和成员函数的使用场合: 一般情况下,建议一元运算符使用成员函数,二元运算符使用友元函数 如果++作为后缀使用,则需要一个额外的参数,用于和前缀区分开 一元运算符 作为成员函数,0个参数;作为友元函数,1个参数 二元运算符 作为成员函数,1个参数;作为友元函数,2个参数 1、运算符的操作需要修改类对象的状态,则使用成员函数。 如需要做左值操作数的运算符(如=,+=,++) 2、运算时,有数和对象的混合运算时,必须使用友元 3、二元运算符中,第一个操作数为非对象时,必须使用友元函数。 如输入输出运算符<<和>> 具体规则如下: 运算符 建议使用 所有一元运算符 成员函数 =()[] -> 必须是成员函数 +=-=/=*=^=&=! = %=>>=<<=,似乎带等号的都在这里了. 成员函数 所有其它二元运算符,例如: –,+,*,/,% 友元函数 <<>> 必须是友元函数 2.->运算符的重载 ->操作符看起来像二元操作符: 接受一个对象和一个成员名。 但是,其实箭头操作符是一元操作符。 况且->的右操作数不是表达式。 如何开始: 由a开始,分下面两种情况 1、a是指针,那么就是我们熟悉的,指向我们a类型的成员数据或函数“b”;到这里,就结束了! 2、a是对象,那么a必须有成员函数"operator->"(否则会报错)。 那么就调用a的operator->函数。 由于operator->返回的可能是指针,也可能是对象,那么就又进入了下一轮递归: 是指针还是对象,走1或2,如此递归。 如何结束: 不管a是指针或者对象,最终都是走到指针结束(当然,写错了也走不下去)。 代码 #include usingnamespacestd; classA { public: voidaction() { cout<<"ActioninclassA! "< } }; classB { Aa; public: A*operator->() { return&a; } voidaction() { cout<<"ActioninclassB! "< } }; classC { Bb; public: Boperator->() { returnb; } voidaction() { cout<<"ActioninclassC! "< } }; intmain(intargc,char*argv[]) { C*pc=newC; pc->action(); Cc; c->action(); getchar(); return0; } 代码分析 C*pc=newC; pc->action(); 输出的结果是 ActioninclassC! 这个结果比较好理解,pc是类对象指针,此时的箭头操作符使用的是内置含义,故pc直接调用其成员函数action。 而下面的代码 Cc; c->action(); 输出的结果是 ActioninclassA! 其实c->action()的含义与c.operator->().operator->()->action();相同。 c是对象,c后面的箭头操作符使用的是重载箭头操作符,即调用类C的operator->()成员函数,此时返回的是类B的对象。 所以接着调用类B的operator->()成员函数,此时返回的是类A的指针。 最终由A的指针调用A的action()成员函数。 注释: 这里存在一个递归调用operator->()的过程,直到最后使用了一次内置含义的箭头操作符。 参考博客: 两个整数相除保留两位小数 int a = 100; int b = 3; float c = (float)a / (float)b; String aa = FormatFloat("0.00",c); c语言二维数组a【0】与a与a【0】【0】有什么区别? 数组在内存中是连续按行分布的,对于a[2][3]={{1,2,3},{4,5,6}};它在内存中的分布式1,2,3,4,5,6;所以他等价于a[2][3]={1,2,3,4,5,6};在c/c++中,数组名也即是数组首地址,这里加入a=0x1000;那么它的地址分就是: 1000,1004,1008,100c,1010,1014;&a[0][0]也就取第一个元素的地址,即1000,a[0]是第一行1000,1004,1008的首地址,也是1000,也即是a元素的首地址; int*b=a[0]; *b和a[0]是等的,是a数组第一行首地址;不同的是a[0]是只读的,*b是可读写的因为b是指针变量,你可以试试a[0][3],a[0][4],a[0][5]都能打印出来,别看a[2][3]以为这样越界了,其实越不越界是按地址来算的,地址上讲,&a[0][3]=a[1],所以完全没问题;按照地址计算上面打印的值就是4,5,6;同理用*(b+3),*(b+4),*(b+5)也是一样的道理; a就是数组的首地址,a[0]是第一行第一个元素的首地址,也即是a的首地址,&a[0]是第一行的首地址,从数值上讲,没问题,只是数据类型上不一样; a[0][0]就是取第一行第一个元素的值,也即是1 int变量转换成二进制 #include #include usingnamespacestd; inta=-2; intb=4; //cout< cout<<(a&b)< Char*指针与char数组 #include #include usingnamespacestd; intmain() { chara[6]="hello"; cout< cout< cout<<(void*)(a+1)< cout< char*p=NULL; p=a; cout< strings="hello"; cout<<(void*)&s[0]< cout<<(void*)&s[1]< return0; } 注意: 1、&a表示的是指针变量a的地址,a表示的是数组的首地址,两者数值上相同。 2、用cout输出a,要通过(void*)进行转换,转换之后输出的是16进制的地址; 用(int)转换,输出的是十进制数值。 一个字符如’a’,占一个字节,由8位二进制组成。 指针每自增1,由于字符只占一个字节,所以指针在寻址的时候就是一个字节的寻址,数值上只增加1 00010100是一
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- c+ 学习 笔记