1 成员函数后边加const
用const修饰函数的返回值:函数返回值的内容不能被修改
const成员函数:加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改
成员函数后面加const是修饰this指向的对象的
class A
{
public:
f(int a);
}
在类A中,有成员函数f。这里f其实有两个参数,第一个是A* const this, 另一个才是int类型的参数。
如果我们不想f函数改变参数的值,可把函数原型改为f(const int),但如果我们不允许f改变this指向的对象呢?因为this是隐含参数,const没法直接修饰它,就加在函数的后面了,表示this指针的类型是 const A* const this。
const本质是修饰*this。
2 size_t 与 size_type
在C++中,设计size_t就是为了适应多个平台的,size_t的引入增强了程序在不同平台上的可移植性。size_t是针对系统定制的一种数据类型,一般是整型。一般这种类型都会定义它具体占几位内存。当然,有些是编译器或系统已经给定义好的。经测试发现,在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的,这样利用该类型可以增强程序的可移植性。
size_t是全局定义的类型;size_type是STL类中定义的类型属性,用以保存任意string和vector类对象的长度。
下标访问元素,vector使用vector<int>::size_type类型,而数组则使用size_t。vector使用的下标实际也是size_t。
3 虚基类
虚基类的作用:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该简介共同基类数据成员的多份同名成员,在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其唯一地标识一个成员。
class A //声明基类A
{...}
class B: virtual public A //声明B是类A的公用派生类,A是B的虚基类
{...}
class C: virtual public A //A是C的虚基类
{...}
虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因此,一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
4 new / delete 与 malloc / free关系
new / delete是运算符,malloc / free是库函数。它们都可用于申请动态内存和释放内存。
new / delete会自动调用对象的构造函数 / 析构函数。malloc / free只会动态申请 / 释放内存
对于非内部数据类型的对象,光用maloc / free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。
5 detele与delete []区别
delete只会调用一次析构函数,而delete [] 会调用每一个成员的析构函数。delete与new配套,delete [] 与new [] 配套
6 C++有哪些面向对象的性质
抽象:类是对象的抽象,而对象是类的具体化
封装:将实例抽象得到的数据和行为封装成一个类
继承:使用现有类的所有功能,在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程,达到代码重用的目的。继承有三种方法:实现继承、接口继承、可视化继承
多态:C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖或者称为重写。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。用基类的指针或引用指向派生类的对象,然后通过基类的指针调用派生类的成员函数
7 子类析构时要调用父类的析构函数吗
构造函数调用次序:先基类的构造,后派生类的构造。与析构的时候恰好相反。
析构函数调用次序:先派生类的析构,后基类的析构。也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了。
8 多态,虚函数,纯虚函数
多态:编译和运行,运行时的多态性通过继承和虚函数来体现;编译时多态性体现在函数和运算符的重载上;
虚函数:
纯虚函数:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在,一般不能直接被调用。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
9 什么是“引用”,声明和使用“引用”需要注意哪些问题
引用是某个目标变量的“别名”,对引用的操作与对变量直接操作效果完全相同。
声明一个引用的时候,一定要对其进行初始化。不能再把该引用名作为其他变量名的别名。
声明一个引用,不是新定义了一个变量,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
10 将“引用”作为函数参数有哪些特点
(1)传递引用给函数与传递指针的效果是一样的。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,
11 什么时候需要使用“常引用”
既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
12 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则
格式:函数返回类型& 函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回值的副本。(注意:正是因为这个原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:
(1)不能返回局部变量的引用
(2)不能返回函数内部new分配的内存的引用
(3)可以返回类成员的引用,但最好是const的
13 结构与联合有声明区别
(1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
(2). 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
14 重载(overload)与重写(override)的区别
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义父类虚函数的方法。
15 有哪几种情况只能用intialization list而不能用assignment
当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
16 C++是不是类型安全的
不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
17 main函数执行以前,还会执行什么代码
全局对象的构造函数会在main 函数之前执行。
18 简述数组名与指向数组的指针的区别
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
19 请说出const与#define相比,有何优点
const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。
(1)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
20 描述内存分配方式以及它们的区别
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多
21 int (*s[10])(int)表示的是什么
int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函数。
22 int id[sizeof(unsigned long)];这个对吗?为什么?
正确 这个 sizeof是编译时运算符,编译时就确定了 ,可以看成和机器有关的常量。
23 引用与指针有什么区别?
1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3) 不存在指向空值的引用,但是存在指向空值的指针。
24 基类的析构函数不是虚函数,会带来声明问题?
派生类的析构函数用不上,会造成内存资源的泄漏。
基类指针指向了派生类对象,而基类中的析构函数是非virtual的,而虚构函数是动态绑定的基础。现在析构函数不是virtual的,因此不会发生动态绑定,而是静态绑定,指针的静态类型为基类指针,因此在delete的时候只会调用基类的析构函数,而不会调用派生类的析构函数。这样,在派生类中申请的资源就不会得到释放,就会造成内存泄漏,这是相当危险的。如果系统中有大量的派生类对象被这样创建和销毁,就会有内存不断的泄漏,久而久之,系统就会因为缺少内存而崩溃。
25 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?
生命周期不同:
全局变量随主程序创建和创建,随主程序销毁而销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;
使用方式不同:
通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。
操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。