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:
templateToperator()(Tt)const{return-t;}
};
intmain()
{
GenericNegatenegate;
cout<cout<}
4.标准库中函数对象
C++标准库定义了几个有用的函数对象,它们可以被放到STL算法中。
例如,sort()算法以
判断对象(predicateobject)作为其第三个参数。
判断对象是一个返回Boolean型结果的
模板化的函数对象。
可以向sort()传递greater<>或者less<>来强行实现排序的升序或降序:
#include//forgreater<>andless<>
#include//forsort()
函数对象用于求最大值
#include
#include
#include
usingnamespacestd;
template
constObject&findMax(constvector
{
intmaxIndex=0;
for(inti=1;iif(cmp.isLessThan(arr[maxIndex],arr[i]))//此时的isLessThan还不知道具体的定义,要看cmp到底是什么
maxIndex=i;//maxIndex每次取到的都是i值,而且只有当arr[maxIndex]returnarr[maxIndex];
}
classCaseInsensitiveCompare
{
public:
//true:
lhslhs>rhs
boolisLessThan(conststring&lhs,conststring&rhs)const
{
return_stricmp(lhs.c_str(),rhs.c_str())<0;//使用string的成员函数,不区分大小写
//returnlhs}
};
intmain()
{
vectorarr(4);
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<>(a)<cout<<(a&b)<Char*指针与char数组
#include
#include
usingnamespacestd;
intmain()
{
chara[6]="hello";
cout<(a)<cout<>((int)(a))<cout<<(void*)(a+1)<cout<>((int)(a+1))<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是一