=>1
}
NOTE1:
(int&)a把a强制转换成整形引用类型不经过转换,直接得到a在内存单元的值
(int)a把a强制转化为整型经过转换了
(int)&a把a的地址强制转换成整型
NOTE2:
float类型在内存中存储的形式是,符号位指数尾数
由754标准:
阶码采用增码(该数补码的反符号),尾数采用原码
所以1.0f在内存中的形式为
00111111100000000000000000000000符号位指数尾数
00000001//源码
11111110+1=11111111//补码=>取反加1
01111111//增码=>符号取反
所以输出的是0x3f800000
0在内存中的的存储形式
00000000000000000000000000000000
3)单目>算数>移位(<<>>)>关系(>>=<<===!
=)>逻辑(&^|&&||)
NOTE1:
运算符的优先级,所以是先对a求反,然后再右移5位
NOTE2:
类型转换,a会先被提升到int型进行运算,最后取低8位
答案:
C
4)通过位运算求X与Y的平均值
解析:
(x&y)+((x^y)>>1)是用来求x和y的平均数的,即(x+y)/2.
(x&y)+((x^y)>>1),把x和y里对应的每一位(指二进制位)都分成三类,每一类分别计算平均值,最后汇总。
其中,一类是x,y对应位都是1,用x&y计算其平均值;一类是x,y中对应位有且只有一位是1,用(x^y)>>1计算其平均值;还有一另是x,y中对应位均为0,无须计算。
下面分别说明一下前两种情况是怎样计算的:
x,y对应位均为1,相加后再除以2还是原来的数,如两个00001111相加后除以2仍得00001111,这是第一部分。
第二部分,对应位有且只有一位为1,用“异或”运算提取出来,然后>>1(右移一位,相当于除以2),即到到第二部分的平均值。
第三部分,对应位均为零,因为相加后再除以二还是0,所以不用计算。
三部分汇总之后就是(x&y)+((x^y)>>1)
顺便解释一下前面说到可以避免溢出。
假设x,y均为unsignedchar型数据(0~255,占用一字节),显然,x,y的平均数也在0~255之间,但如果直接x+y可能会使结果大于255,这就产生溢出,虽然最终结果在255之内,但过程中需要额外处理溢出的那一位,在汇编中就需要考虑这种高位溢出的情况,如果(x&y)+((x^y)>>1)计算则不会。
5)通过位运算交换a与b的值
a=a^b;b=a^b;a=a^b;//无需担心越界问题
6)C语言不支持函数重载
7)宏中的参数要注意加括号
答案:
#defineMIN(A,B)((A)<=(B)?
(A):
(B))
8)const相关说明
A.const与#define:
作用域,有类型,方便调试(C中只能用#define表示常量)
B.C与C++中的const
a)C中const的意思是”一个不能被改变的普通变量”,所以下面的语句是错误的
constbuffersize=100;
charbuf[buffersize];//错误,buffersize不是常量
b)C中const默认是外部链接的,所以可以有如下的语句
constbuffersize;//C认为它是一个声明,定义及初始化在其他地方了
而在C++中如果要做同样的事情需要如下的语句
externconstbuffersize;//C++默认const是内部链接的
C.C++中的const
voidf()const如果想修改类中的非const变量,需要在该变量前加mutable(如mutableinti)
voidf()与上面的函数重载
9)Sizeof问题之数据对齐
A.charq2[]=”a\n”;
sizeof(q2);//应该为3
B.struct{shorta1;shorta2;shorta3;}A;
sizeof(A);//6short为2
C.struct{longa1;shorta2;}B;
sizeof(B);//8long为4
D.classD{
public:
inta;
staticintb;
D();
~D();
}
sizeof(D);//4sizeof计算栈中分配的大小,静态变量是存放在全局数据区的
E.classE{
public:
doublea;
floatb;
intc;
chard;
E();
~E();
}
Sizeof(E);//24怎么回事呢?
?
?
?
?
NOTE:
结构体默认的字节对齐一般满足三个准则:
1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要编译器会在成员之间加上填充字节(internaladding);
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailingpadding)。
10)Sizeof小结
A.对函数使用sizeof,在编译阶段会被函数返回值的类型替代
例如:
intfun();sizeof(fun())等同于sizeof(int)
B.Sizeof不是函数也不是一元运算符,它是类似于宏定义的特殊关键字,sizeof()括号内的内容在编译过程是不会被编译的,而是被类型替代
inta=8;
sizeof(a=6);//结果等同于sizeof(int),而且运行完a的值仍然是8,因为括号内的内容不会被编译
11)Sizeof与类
A.空类所占的空间为1,多重继承的空类所占的空间为1
B.含有虚函数的类空间要加4
12)常见错误1:
使用前一定要将指针指向一块内存
错误1:
int*pa;
*pa=5;//错误,pa还没指向一块内存
错误2:
该函数不能达到交换两个数的效果,因为temp没指向一块内存就赋值了
13)常见错误2:
传值与传址
程序运行时会崩溃,str一直都是NULL,因为是传值调用的
14)类中的相对偏移问题
解析:
B*pb=(B*)(&a);//强制将a地址的内容转化为一个指向B类型的指针
由于intm_a与intm_c的内存偏移量相同
所以,本体的答案为1
15)各种指针,函数指针,数组指针,指针数组
16)指针之间的强制转换
问题1:
答案:
25
解析:
&a:
类型为int(*)[5],加1则整体加一行,&a+1指向数组中5的后面
int*ptr=(int*)(&a+1):
通过指针之间的强制转换将&a+1(int(*)[5])转化成(int*)
ptr-1:
ptr是普通的指向int的指针,ptr-1指向数组中的5
问题2:
inta[4]={1,2,3,4};
char*pt=(char*)&a;//等价于char*pt=(char*)&a;
printf("%d%d",*(a+1),*(int*)(pt+4))
答案:
22
解析:
pt为指向数组a首元素的char型指针,pt+4移动4个字节
问题3:
宏定义求结构体内变量的偏移(注意:
不能以分号结束)
解析:
->的优先级大于(type)的优先级,所以要((struc*)0)->e
NOTE:
A.指针是什么呢?
一句话,指针其实就是地址。
只要有了一个地址,就可以访问到特定的内存。
这就是指针。
因为用途不同(可能指向一个int啊,也可能指向一个double啊),所以指针也分很多种。
这些指针本质上都是一样的(都是内存的地址嘛),如果指向一个地方,那么值也是一样的(同一个地方的地址自然只有一个咯)。
可以通过强制转换将一块内存转换成各种类型的指针
B.如何将一个指针强制转化为指向数组的指针
typedefint(*p)[3];
int(*ap)[3]=(p)ip;//这样
int(*ap)[3]=(int(*)[3])ip;//或这样
typedefint(*p)[3];
int(*ap)[3];
ap=(p)ip;//这样
ap=(int(*)[3])ip;//或这样
C.指针强制转换后的结果是临时变量,不能作为左值
17)malloc/free与new/delete的区别
18)智能指针
答案:
BE
NOTE:
B,C格式不对,D是错的,因为auto_ptr放在vector中是不合理的,
19)深拷贝与浅拷问题
解析:
A.vector*a1=newvector();
a1->push_back(d1);//会调用CDemo的默认拷贝构造函数,传值调用时会调用拷贝构造函数进行形参与实参的赋值
B.deletea1;//释放vector对象,vector所包含的所有元素也同时被释放
C.局部变量CDemod1在main函数退出时,也会释放所占的内存空间
D.这样str所指向的内存空间就会被释放两次了,引起内存错误
E.采用深拷贝可以解决问题
20)泛型编程1template
21)泛型编程2函数指针
方法1:
使用函数指针
方法2:
使用静态模板类
22)STL常用数据结构总结----------------------------------------------------
23)默认构造函数
NOTE1:
A.如果类提供了其他的构造函数,则编译器不会提供默认构造函数了,此时需要自己定义默认构造函数(无参或是默认参数)
B.只有当一个类没有定义构造函数的时候,编译器才会自动生成一个默认构造函数
C.没有默认构造函数的类不能用作动态分配数组的元素类型
D.没有默认构造函数的类的静态分配数组必须为每一个元素提供一个显示的初始化式
E.对象的数组和使用容器时可能会用到缺省的构造函数(动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数 )
NOTE2:
非堆数组可以,但是,定义了几个元素就要在初始化值集中写几个
CLSa[3]={CLS(para),CLS(para),CLS(para)};
堆数组不可以
NOTE3:
A.动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化。
string*psa=newstring[10];
int*pia=newint[10];
这两个new表达式都分配了10个对象的数组,第一个数组是string类型,分配了存储对象的空间后,会调用string类型的默认构造函数依次初始化数组中的每个元素。
第二个数组为内置类型,分配了存储10个int对象的内从空间,并没初始化
B.可以跟在数组后面加一对空圆括号,对数组元素进行初始化。
int*pia2=newint[10]();
C.圆括号要求编译器对数组初始化,这里把数组元素都设置为0。
对于动态分配数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。
NOTE4:
如果vector保存的是含构造函数的类类型的元素,则将使用该类型的默认构造函数进行初始化,例如
vectorsvec;//10elements,eachanemptystring
24)const数据成员的初始化
NOTE:
A.const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。
因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
B.不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
const数据成员的初始化只能在类的构造函数的初始化列表中(区分初始化与赋值)进行。
C.要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者staticconst。
25)拷贝构造函数与赋值函数
classString{
public:
String(constchar*str=NULL);//默认构造函数
String(constString&other);//拷贝构造函数
String&operator=(constString&other);//赋值操作符
private:
char*m_data;
}
String(constchar*str)
{
If(str==NULL)
{
m_data=newchar[1];
*m_data=’\n’;
}
Else
{
Intlength=strlen(str)+1;
m_data=newchar[length];
strcpy(m_data,str);
}
}
//拷贝构造函数的调用情况是在对象初始化时,
//此时m_data还没分配内存,所以不需要考虑释放内存
String(String&other)
{
Intlength=strlen(other.m_data)+1;
m_data=newchar[length];
strcpy(m_data,other.m_data);
}
A.函数返回的临时对象一般都是const对象
Strings1(“hello”);
Strings2(“world”);
Strings3=s1+s2;其中s1+s2产生一个临时变量
这也是为什么operator+重载时返回值为const的原因
B.非const引用只能绑定到与该引用同类型的对象。
const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
(隐式转化常常返回临时变量,而临时变量是const类型的)
所以,
float j = 2, int& i = j不可
float j = 2, const int& i = j可。
String&operator(constString&other)
{
If(this==&other)
Return*this;//证同测试
char*pOld=m_data;
intlength=strlen(other.m_data)+1;
m_data=newchar[length];
strcpy(m_data,other.m_data);
deletepOld;
return*this;//千万别忘了
}
26)拷贝构造函数
NOTE:
这种说法是正确的,因为string对象与vector对象的类都有合适的赋值操作
27)模板类的友元重载
28)覆盖问题1
29)覆盖问题2
#include
usingnamespacestd;
classA
{
protected:
intm_data;
public:
A(intdata=0){m_data=data;}
intGetData(){returndoGetData();}
virtualintdoGetData(){returnm_data;/*m_data=0*/}
};
classB:
publicA
{
protected:
intm_data;
public:
B(intdata=1){m_data=data;}
//这里A中的m_data=0,B中的m_data=1
intdoGetData(){returnm_data;/*m_data=1*/}//实现接口
};
classC:
publicB
{
//C继承了A&B类的方法&属性,且未从新定义接口,故接口还是B类中定义的
protected:
intm_data;
public:
C(intdata=2){m_data=data;}
//这里A中的m_data=0,B中的m_data=1,C类中的m_data=2
};
intmain()
{
Cc(10);
cout<cout<:
GetData()<cout<:
GetData()<cout<:
GetData()<cout<cout<:
DoGetData()<cout<:
DoGetData()<cout<:
DoGetData()<return0;
}
解析:
A.从基类开始调用构造函数,A,B,C的m_data的值是不同的
B.由于子类中没有显示的初始化父类的数据成员,所以父类调用默认的构造函数
classA
{
public:
inta;
intb;
A(intx,inty):
a(x),b(y)
cout<};
classB:
publicA
{
public:
//显示初始化父类对象
B(intx,inty):
A(x,y)
cout<};
cout<:
GetData()<//为什么这里不是调用A中的doGetData()呢。
而是调用B中的doGetData()
C.在初始化的时候,每个函数都有特定的地址。
在执行c.A:
:
GetData()的时候,没有二义性,唯一确定的执行了A:
:
GetData()这个函数
D.同时c的地址被作为A*constthis传入A:
:
GetData()函数。
执行了this->doGetData()函数
E.看A的声明,doGetData()函数为虚函数,无法一次确定执行的函数的地址,而是通过C类的虚函数表,由于this指针实际指向了main中的c对象,即虚函数表中的doGetData()函数被覆盖,于是执行了B:
:
doGetData()
c.GetData():
C的对象调用从基类继承下来的成员函数GetData(),此刻的GetData()还是属于A的,为什么呢?
因为,类的一般的成员函数,只有一份,系统只维护一份。
接着这个GetData()函数里面又去调用另外一个方法,而这个方法是虚方法,问题就来了--->到底调用哪个虚方法呢?
这就是多态,此时,对象c,会去根据这个对象的vptr,遍历C类的虚函数表,找到个虚方法(虚方法都放在虚函数表中)。
所以,调用的是B类的虚方法,因为C继承自B,而C类中未改写B类的这个虚方法,所以是B的虚方法。
c.A:
:
GetData():
这个动作,编译的时候静态绑定调用的是A:
:
GetData(),明确说了,是A的成员函数,但里面调用的却是一个虚方法,需要动态绑定。
最终:
结果和上面分析的一样。
c.B:
:
GetData(),c.C:
:
GetData():
情形都和上面的一样。
c.DoGetData():
这个动作,直接调用虚方法。
对象c根据自己的vptr,去vtbl中找到这个虚方法来,调用它,就OK。
(在派生类的对象中对基类的成员进行操作)
c.A:
:
DoGetData():
这个动作,是在编译的时候,就静态绑定了,调用哪一个虚函数
c.B:
:
DoGetData(),c.C:
:
DoGetData():
情形和上面的一样。