1. C++对空类或者空结构体,对其sizeof操作时候,默认都是1个字节。
2. 下列程序输出的结果:12
#include<iostream>
using namespacestd;
class A{
public:
int _a;
A(){
_a=1;
}
void print() {
cout<<_a;
}
};
class B:public A {
public:
int _a;
B() {
_a=2;
}
};
int main() {
B b;
b.print();
cout<<b._a;
return 0;
}
分析:因为在继承的时候,子类存在与父类同名的成员变量,则子类会屏蔽父类的成员变量。因为给孩子类中没有定义print函数,所以会按照就近原则去寻找父类中是否有print函数。恰好父类中有这个函数,于是调用父类的print函数b.print(),而这个函数会调用父类的a变量。
子类公有(public)继承父类,所以子类可以通过对象访问父类的公有成员函数,由于调用的是父类的公有成员函数(该函数中的this指针存放的是父类对象的地址),所以打印的是父类A的_a。
如果在B类增加一个void print(){cout<<_a;}函数的话,打印就是22
3. 委托构造是对同一类而言,参数类型不同的构造函数可以调用字节的其他构造函数。
4. pwrite函数是系统调用。
5. 下列程序输出结果:ink
#include<iostream>
using namespace std;
int main() {
static char*s[]={"black","white","pink","violet"};
char**ptr[]={s+3,s+2,s+1,s},***p;
p=ptr;
++p;
printf("%s",**p+1);
return 0;
}
分析:首先 s是一个指针数组,存的是字符串的首地址。
char **ptr[]是一个指针数组,不过是一个二级指针数组,存的是s这个数组中,每个元素的地址。p是一个三级指针,ptr这个二级指针数组的数组名也是一个三级指针, p++只是往后跳了一个元素的位置,*p就是第二个元素的内容,也就是指向s数组中s+3这个元素的地址,**p就得到了s数组中s+3这个元素的内容,s数组中保存的是字符串的首地址,那就得到了"pink"这个元素的首地址, 在**p+1就是这个地址向后偏移一个字节,也就指到了'i'上,所以%s,**p+1还是"ink"。
6.首先要明白变量初始化的顺序是其声明的顺序,跟初始化列表中的顺序无关。所以变量的初始化顺序为m_nFir(i++),m_nSec(i++),m_nThd(i++),&m_nFor(m_nThd);
i初始值为1,所以经过初始化列表初始化以后m_nFir=1,m_nSec=2,m_nThd=3,m_nFor为m_nThd的一个引用。然后i的值为4,构造函数中执行语句m_nThd=i后,m_nThd=4,m_nFor是它的一个引用,自然值也为4。输出结果m_nFir+m_nSec+m_nThd+m_nFor=11。
6. 一个C++语言程序是由函数组成。
7. 派生类强制转化成基类(dynamic_cast)会做相应成员变量和函数的裁剪,仅能访问从基类继承来的部分成员;
派生类可以访问基类对象的protected成员;(派生类如果要访问基类protected成员只有通过派生类对象,派生类不能访问基类对象的protected成员)
8. 负数求补码是取反再加1,其实和取反加1是一样的结果。即“负数的补码是原数值除符号位按位取反再加一。
9. 实型常量又称实数或浮点数。在C语言中可以用两种形式表示一个实型常量。
小数形式:
小数形式是由数字和小数点组成的一种实数表示形式,例如0.123、.123、123.、0.0等都是合法的实型常量。
注意:小数形式表示的实型常量必须要有小数点。
指数形式:
这种形式类似数学中的指数形式。在数学中,一个可以用幂的形式来表示,如2.3026可以表示为0.23026×10^12.3026×10^0 23.026×10^-1等形式。在C语言中,则以“e”或“E”后跟一个整数来表示以“10”为底数的幂数。2.3026可以表示为0.23026E1、2.3026e0、23.026e-1。C语言语法规定,字母e或E之前必须要有数字,且e或E后面的指数必须为整数。如e3、5e3.6、.e、e等都是非法的指数形式。注意:在字母e或E的前后以及数字之间不得插入空格。
程序运行的过程中,其值不能被改变的量称为常量。常量有不同类型,其中12、0、-5为整型常量。'a''b'为字符常量。而4.6、-8.7则为实型常量。
一个实型常量可以赋给一个 float 型、double 型或 long double 变量。根据变量的类型截取实型常量中相应的有效位数字。
9. 下列程序输出:Garbage value
#include<iostream>
using namespace std;
int main() {
int i=11;
int const*p=&i;
p++;
cout<<*p;
return 0;
}
Garbage value为垃圾值,代表*p是没有值的东西。
10.构造函数和析构函数是两个特殊成员函数,这个函数名字和类名相同,构造函数在定义对象时候由系统自动调用,析构函数在删除对象时自动调用。
使用表达式“new”创建类对象时,系统自动调用类的构造函数,需要注意:
1) 构造函数的名称必须与类名称相同;
2) 构造函数没有返回值,使用不能有返回值;
3) 构造函数不能声明为const类型;
4) 任何类都最少有一个构造函数,若程序不提供,系统会提供一个默认构造函数,默认构造函数不带任何参数;(构造函数只能定义为public类型)
析构函数不是必须的,析构函数与类名相同,在前面加一个~符号,没有任何参数,不返回任何值。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译器自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。
11.对于以下数据结构data的处理方式描述正确的是(C)
struct Node{
int size;
char data[0];
};
C:编译器会认为这是一个长度为0的数组,而且会支持对于数组data的越界访问;
分析:柔性数组,它只能放在结构体末尾,是申明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量(注意:数组名永远都不会是指针!),但对于这个数组的大小,我们可以进行动态分配。仔细理解后半部分,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量!对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等。
注意:构造缓冲区就是方便管理内存缓冲区,减少内存碎片化,它的作用不是标志结构体结束,而是扩展柔性数组是C99的扩展,简而言之就是一个在struct结构里的标识占位符(不占结构struct的空间)
12.记住: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”。所以大家还是记住上述结论:绝不重新定义继承而来的缺省参数值!