1,对象数组、构造与析构
对于题目:
A *pa = new A[5];
delete pa;
在上面的代码中:
构造函数执行了5次,pa
相当于数组的第一个元素地址。
而delete pa
只释放第一个元素,所以析构函数只调用了一次。
如果我们改为delete[] pa
,则会释放所有元素,即析构函数会调用五次。
2,类型之间的转换
当涉及两种类型时:
- 较小的类型就会被转换为较大的类型
- 表达式低的类型就会被转换为表达式高的类型
能力从低到高,依次是:
- char
- int
- unsigned int
- long
- unsigned long
- float
- double
- long double
对于题目:
char w;
int x;
float y;
double z;
对于表达式w*x+z-y
的类型,最后为double
型。
3,指针问题
对于题目:
有定义语句:char s[3][10],(*k)[3],*p;
,则对于赋值语句
p = s;
p = k;
p = s[0];
k = s;
错误的是哪几个?
对于这个题目,主要要弄清楚s、k、p的含义。
- s是一个二维数组,但也可以看成是一个包含3个元素的一维数组,每个元素又是一个包含10个元素的一维数组。
s这个名字本身的值是指向它第一个元素的指针。也就是说,s的值是指向包含3个元素的一维数组的第一个元素的指针。即第一个包含10个元素的一维数组的指针。 - k是一个指针,而不是指针数组。k是指向一个包含3个元素的一维数组的指针。
简单来说:
- s是指向一维数组的常量指针,数组包含10个元素。
- k是指向一维数组的变量指针,数组包含3个元素。
- p是指向单个变量的指针。
如果一个变量算一个单位长,那么s+1就意味着指针走了10个单位长,k+1就意味着走了3个单位长,p+1就意味着走了1个单位长。
char s[3][10]
s 是数组指针,类型为char (*)[3]
,所指向的每个数组长度为10;
char (*k)[3]
很明显k就是一个数组指针,类型也为 char (*)[3]
,但是所指向的每个数组的长度未知;
char *p
类型为char *
指针。
因此,在没有强制类型转换的情况下,只有类型完全相同的指针才能相互复制。
答案:1,2,4
而3为什么正确?
s[0]
是指向第一行数组的首元素的地址,即s[0] == &s[0][0]
,指针类型为char *
,因此可以与char * p
进行复制操作。
如果将s 的定义改成 char s[3][3]
,那么 k=s
就正确了。
或者将k 的定义改成char (*k)[10]
,那么k=s
也正确。
4,fseek函数
对于fseek函数,其原型为:
int fseek(FILE*fp,long offset,int whence)
作用是移动文件到某一个特殊位置。
其中:
- fp是文件指针
- offset为移动的字节数(偏移量)
- whence为0表示从开头移动,为1表示从当前位置开始移动,为2从末尾开始移动
5,编译相关
从代码到运行的过程:
- 编辑
- 编译预处理
- 编译
- 汇编
- 链接
- 运行
6,指针变量与数据类型
对于题目:
指针变量p进行自加运算(即 执行p++;)后,地址偏移值为1,则其 数据类型为 char。说法是否正确?
这种说法是不正确的,因为比如bool
、空类、unsigned char
也都是一个字节,于是偏移量也是1。
7,类的静态绑定问题
对于题目:
#include<iostream>
using namespace std;
class A{
public:
void test()
{ printf("test A"); }
};
int main(){
A* pA = NULL;
pA->test();
return 0;
}
则最后的输出结果是什么?
对于上面的代码,是可以正常运行的。
因为对于非虚成员函数,C++是静态绑定的。
普通成员函数在编译器实现时,大致经历一下转化过程:
1.改写函数的签名以安插一个额外的参数 - this指针
2. 将每一个对非静态成员数据成员的存取操作改为经由this指针来存取。
3.将成员函数重写成外部函数,函数名称经过“mangling"处理,使其在程序中有唯一的名字。
在这道题目中,对于语句pA->test()
,实际意图是:
- 调用对象 pA 的 test 成员函数。
对于C++。为了保证程序的运行时效率,C++的设计者认为凡是编译时能确定的事情,就不要拖到运行时再查找了。所以C++的编译器看到这句话会这么干:
1:查找 pA 的类型,发现它有一个非虚的成员函数叫 test 。(编译器干的)
2:找到了,在这里生成一个函数调用,直接调A:: test ( pA )。
所以到了运行时,由于 test ()函数里面并没有任何需要解引用 pA 指针的代码,所以真实情况下也不会引发segment fault。这里对成员函数的解析,和查找其对应的代码的工作都是在编译阶段完成而非运行时完成的,这就是所谓的静态绑定,也叫早绑定。
因此,这道题的输出为:
test A
8,虚函数问题
在构造函数中调用类的虚函数,虚函数的动态绑定机制会失效。
- 由于类的构造次序是由基类到派生类,所以在构造函数中调用虚函数,这个虚函数不会呈现出多态。
在析构函数中调用类的虚函数,虚函数的动态绑定机制会失效。
- 而对于析构函数而言,类的析构是从派生类到基类,当调用继承层次中某一层次的类的析构函数时往往意味着其派生类部分已经析构掉,所以也不会呈现出多态。
静态函数不可以是虚函数。
- 因为静态成员函数没有this,也就没有存放vptr的地方,同时其函数的指针存放也不同于一般的成员函数,其无法成为一个对象的虚函数的指针以实现由此带来的动态机制。静态是编译时期就必须确定的,虚函数是运行时期确定的。
虚函数可以声明为inline。
- inline函数和virtual函数有着本质的区别,inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制。
因此,内联函数是个静态行为,而虚函数是个动态行为,他们之间是有矛盾的。
函数的inline属性是在编译时确定的, 然而,virtual的性质则是在运行时确定的,这两个不能同时存在,只能有一个选择,文件中声明inline关键字只是对编译器的建议,编译器是否采纳是编译器的事情。
虽然虚函数也同样可以用inline来修饰,但你必须使用对象来调用,因为对象是没有所谓多态的,多态只面向行为或者方法,但是C++编译器,无法保证一个内联的虚函数只会被对象调用,所以一般来说,编译器将会忽略掉所有的虚函数的内联属性。
简单来说,你可以给虚函数加上inline关键字,但是不是意味着你加上了inline关键字这个函数就是内联函数了,是不是内联函数是编译器说了算的,因为内联与否,只是你的一个建议,最终决定权在编译器手中。
设置虚函数需要注意的地方:
- 只有类的成员函数才能被声明为虚函数。
- 静态成员函数不能使虚函数。
- 内联函数不能使虚函数。
- 构造函数不能使虚函数。
- 析构函数可以是虚函数,而且通常被声明为虚函数。
9,指针数组
对于题目:
在32为系统中,定义**a[3][4]
,则变量占用的内存空间大小是多少?
在上面的问题中,a是一个数组,数组的大小为3 * 4,数组中存放着指针的指针,在32为系统下,指针大小4B,所以结果为4 * 3 * 4=48。
10,逗号表达式
对于题目:
以下输出结果为:
int func(int x,int y)
{
return(x+y);
}
main()
{
int a=1,b=2,c=3,d=4,e=5;
printf("%d\n",func((a+b,b+c,c+a),(d+e)));
}
在这个问题中,我们可以看到fun函数的第一个参数为逗号表达式,逗号表达式的值由最后一项所决定,即c+a。
因此,对于这道题,最后的结果为13。