C++学习(48)

来源:互联网 发布:php开源管理后台框架 编辑:程序博客网 时间:2024/06/05 13:27

1. 当派生类中不含对象成员时

    ·在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;

    ·在撤消派生类对象时,析构函数的执行顺序是:派生类的构造函数→基类的构造函数。

 

    当派生类中含有对象成员时

    ·在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;

    ·在撤消派生类对象时,析构函数的执行顺序:派生类的构造函数→对象成员的构造函数→基类的构造函数

 

2. PAT(*ad)[3];

ad首先是个指针;

ad是个指向有着三个PAT元素的数组的指针;

这里只是声明了指针,虽然指针指向的数组有三个PAT对象,但是没有实例化其中的对象,所以并没有调用构造函数


3.缺省参数是静态绑定的,绝不重新定义继承而来的缺省参数。


记住:virtual 函数是动态绑定,而缺省参数值却是静态绑定。 意思是你可能会 在“调用一个定义于派生类内的virtual函数”的同时,却使用基类为它所指定的缺省参数值。


结论:绝不重新定义继承而来的缺省参数值!(可参考《Effective C++》条款37

 

对于本例:

B*p = newB; p->test();

p->test()执行过程理解:

(1)由于B类中没有覆盖(重写)基类中的虚函数test(),因此会调用基类A中的test();

(2)A中test()函数中继续调用虚函数 fun(),因为虚函数执行动态绑定,p此时的动态类型(即目前所指对象的类型)为B*,因此此时调用虚函数fun()时,执行的是B类中的fun();所以先输出“B->”;

(3) 缺省参数值是静态绑定,即此时val的值使用的是基类A中的缺省参数值,其值在编译阶段已经绑定,值为1,所以输出“1”;

 

最终输出“B->1”。所以大家还是记住上述结论:绝不重新定义继承而来的缺省参数值

 

4. 多线程调用时要进行保护时,主要是针对全局变量和静态变量的,函数内的局部变量不会受到影响。

 

这里i是全局变量,j是局部静态变量,所以 要进行保护。

 

5.32位机器上,分析程序,输出10、2、1

void func(char (&p)[10]) {    cout<<sizeof(p)<<endl;}int main() {    printf("%d\n",sizeof(char[2]));    printf("%d\n",sizeof(char&));    return 0;}

分析:第一个p是对char[10]的引用,所以输出为10;第二个p是对数组的操作,与int a[2]sizeof(a);一样;第三个p是直接对数据类型操作,当在传入数据的时候,也就转换为数据类型来操作的,就是简单sizeofchar的操作。

 

根据C++11, When applied to a reference or a reference type, the result is the sizeof the referenced type.。由于是对于char&amp;取sizeof, 实际上是对于char 去大小,char 是 1 byte 的。

 

6.32位机器,分析下述程序:C

signed char a=0xe0;

unsigned int b=a;

unsigned char c=a;

A a>0&&c>0为真      B a==c为真C b的十六进制表示:0xffffffe0D上面都错误

 

分析:同等位数的类型之间的赋值表达式不会改变其在内存之中的表现形式,因此通过 unsignedchar c = a;语句,c的位存储形式还是0xe0

 

对于B选项,编译器首先检查关系表达式"=="左右两边a ,c的类型,如果某个类型是比int的位宽小的类型,就会先进行Integer Promotion,将其提升为int类型,至于提升的方法,是先根据原始类型进行位扩展(如果原始类型为unsigned ,进行零扩展,如果原始类型为signed,进行符号位扩展)至32位,再根据需要进行unsigned to int 形式的转换。


因此:

a为signedchar型,位宽比int小,执行符号位扩展,被提升为0xffffffe0;

c为unsignedchar型,位宽比int小,执行零扩展,被提升为 0x000000e0;

经过以上步骤,再对两边进行比较,显然发现左右是不同的,因此==表达式值为false。

 

再举一个例子:

-------------------------------------

signed int a = 0xe0000000, unsigned int b = a;cout<< (b == a) <<endl;

-------------------------------------

结果为 1, 因为a、b位宽已经达到了int的位宽,均不需要Integer Promotion,只需要对a执行从unsignedto signed的转换,然而这并不影响其在内存中的表现形式,因此和b比较起来结果为真。

 

分析二:将char转换为int时关键看char是unsigned还是signed,如果是unsigned就执行0扩展,如果是signed就执行符号位扩展。跟int本身是signed还是unsiged无关

 

分析三:a为负数,c为正数。负数扩充高位用1来补全。

 

7. static_cast 的用法

 

static_cast < type-id > ( expression )

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换

 

进行上行转换(把派生类的指针或引用转换成基类表示)是安全的

进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的

 

②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

③把空指针转换成目标类型的空指针。

把任何类型的表达式转换成void类型

 

注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。

 

C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为显式类型转换使用。

static_cast会强制覆盖掉编译器的检查构造,所以转换时可行的(但一般会先确认基类向派生类转换时安全的才会使用static_cast)。

 

C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释

 

四类强制转换:

static_cast(编译器可实现的隐式转换或类层次间的下行转换)、dynamic_cast(操作数只能为类指针或类引用)、const_cast(去除const)、reinterpret_const(一般意义强制转换)

 

8. 由于类的构造次序是由基类到派生类,所以在构造函数中调用虚函数,这个虚函数不会呈现出多态; 相反,类的析构是从派生类到基类,当调用继承层次中某一层次的类的析构函数时往往意味着其派生类部分已经析构掉,所以也不会呈现出多态。

 

静态函数不可以是虚函数因为静态成员函数没有this,也就没有存放vptr的地方,同时其函数的指针存放也不同于一般的成员函数,其无法成为一个对象的虚函数的指针以实现由此带来的动态机制。静态是编译时期就必须确定的,虚函数是运行时期确定的

 

虚函数可以声明为inline。inline函数和virtual函数有着本质的区别,inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制

因此,内联函数是个静态行为,而虚函数是个动态行为,他们之间是有矛盾的。

函数的inline属性是在编译时确定的, 然而,virtual的性质则是在运行时确定的,这两个不能同时存在,只能有一个选择,文件中声明inline关键字只是对编译器的建议,编译器是否采纳是编译器的事情

我并不否认虚函数也同样可以用inline来修饰,但你必须使用对象来调用,因为对象是没有所谓多态的,多态只面向行为或者方法,但是C++编译器,无法保证一个内联的虚函数只会被对象调用,所以一般来说,编译器将会忽略掉所有的虚函数的内联属性。

 

什么函数不能声明为虚函数?

一个类中将所有的成员函数都尽可能地设置为虚函数总是有益的。

设置虚函数须注意

1:只有类的成员函数才能说明为虚函数;

2:静态成员函数不能是虚函数;

3:内联函数不能为虚函数;

4:构造函数不能是虚函数;

5:析构函数可以是虚函数,而且通常声明为虚函数。

11. 友元函数重载运算符时,因为没有this指针指向对象,因此参数个数保持和原来一样,运算符至少有一个参数。

 

友元函数重载时,参数列表为1,说明是1元,为2说明是2元;

成员函数重载时,参数列表为空,是一元,参数列表是1,为2元;

 

12.分析下列程序,输出:72

#include<iostream>#include<string.h>using namespace std;int main() {    char a=101;    int sum=200;    a+=27;sum+=a;    cout<<sum;    return 0;}

分析:char类型的范围是-128---+127,当a+=27 ,之后a的值超出可表示范围会变为-128.接着往下计算就是72.

 

13.假设指针变量p定义为int *p=new int(100); 要释放p所指向的动态内存,应该使用语句:A

A delete p       B delete *p C delete &p  D delete []p;

分析:一般用法是new一个数组的话一般是delete [] ,其他的直接delete即可。

 

int* p = new int (100) 是创建一个int型的内存,并赋值为100;

int *p = new int[100] 是创建100个int型的内存;

但是其实对于内置数据类型,其实是delete[] 和delete都可以的。

 

14.分析下述程序:

#include<iostream>#include<string.h>using namespace std;void test(void *data) {    unsigned intvalue=*((unsigned int *)data);    printf("%u",value);}int main() {    unsigned intvalue=10;    test(&value);    return 0;}

分析:注意void test(void *data),参数类型是void,所以先要进行指针转换(unsigned int *)然后再取值。

 

实际上只要是*data,我们就知道了它是指针,如果是32位机器,该指针就指着内存中的某个地址,用32位表示,记住这个32位只是初始地址,任何指针都是的。而前面的void 或者int 类型是定义一次读几个字节,如果是int则读4个字节,也就是从*data存的地址开始从内存往后读4个字节就行,而void是空,没有指定要读多少个字节,所以要用指针类型(unsignedint *)强制转化为知道要读几个字节的int指针,然后再用*从开始地址,读取unsigned int个字节出来。

 

15. A 项错误,因为使用 inline 关键字的函数只是用户希望它成为内联函数,但编译器有权忽略这个请求,比如:若此函数体太大,则不会把它作为内联函数展开的。

 

B 项错误,头文件中不仅要包含 inline 函数的声明,而且必须包含定义,且在定义时必须加上 inline 。【关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用】

 

C 项错误, inline 函数可以定义在源文件中,但多个源文件中的同名 inline 函数的实现必须相同。一般把 inline 函数的定义放在头文件中更加合适。

 

D 项正确,类内的成员函数,默认都是 inline 的。【定义在类声明之中的成员函数将自动地成为内联函数

 

EF 项无意思,不管是 class 声明中定义的 inline 函数,还是 class 实现中定义的 inline 函数,不存在优先不优先的问题,因为 class 的成员函数都是 inline 的,加了关键字 inline 也没什么特殊的

 

16. 类的大小只与成员变量(非static数据成员变量)和虚函数指针有关,还要考虑到对齐。

因为在基类中存在虚函数时,派生类会继承基类的虚函数,因此派生类中不再增加虚函数的存储空间(因为所有的虚函数共享一块内存区域),而仅仅需要考虑派生类中添加进来的非static数据成员的内存空间大小。

 

C++ Primer Plus 第六版中文版,P504:编译器为每个类对象维持一个隐藏的成员,它是一个指向[虚函数地址数组]的指针。虚函数地址数组中存储了类对象声明的虚函数的地址,若在派生类中新添加了一个虚函数,则该函数地址也会被添加进虚函数地址数组中。

17. A:类方法是指类中被static修饰的方法,无this指针。

C:类方法是可以调用其他类的static方法的

D:静态方法访问静态变量,非静态方法可以访问静态变量,错在绝对二字。

 

成员方法又称为实例方法

静态方法又称为类方法

a,静态方法中没有this指针

c,可以通过类名作用域的方式调用Class::fun();

d,太绝对化了,在类中申请一个类对象或者参数传递一个对象或者指针都可以调用。

 

 

原创粉丝点击