简述下C++语言的特点
- C++在C语言基础上引入了面对对象的机制,同时也兼容C语言。
- C++有三大特性(1)封装。(2)继承。(3)多态;
- C++语言编写出的程序结构清晰、易于扩充,程序可读性好。
- C++生成的代码质量高,运行效率高,仅比汇编语言慢10%~20%;
- C++更加安全,增加了const常量、引用、四类cast转换(static_cast、dynamic_cast、
const_cast、reinterpret_cast)、智能指针、try—catch等等; - C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库
STL(Standard Template Library)。 - 同时,C++是不断在发展的语言。C++后续版本更是发展了不少新特性,如C++11中引入了
nullptr、auto变量、Lambda匿名函数、右值引用、智能指针。
说说C语言和C++的区别
- C语言是C++的子集,C++可以很好兼容C语言。但是C++又有很多新特性,如引用、智能指针、auto变量等。
- C++是面对对象的编程语言;C语言是面对过程的编程语言。
- C语言有一些不安全的语言特性,如指针使用的潜在危险、强制转换的不确定性、内存泄露等。而C++对此增加了不少新特性来改善安全性,如const常量、引用、cast转换、智能指针、try—catch等等;
- C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL。 C++的STL库相对于C语言的函数库更灵活、更通用。
说说 C++中 struct 和 class 的区别
- struct 一般用于描述一个数据结构集合,而 class 是对一个对象数据的封装;
- struct 中默认的访问控制权限是 public 的,而 class 中默认的访问控制权限是 private 的,例如:
struct A{
int iNum; // 默认访问控制权限是 public
}
class B{
int iNum; // 默认访问控制权限是 private
}
- 在继承关系中,struct 默认是公有继承,而 class 是私有继承;
- class 关键字可以用于定义模板参数,就像 typename,而 struct 不能用于定义模板参数,例如:
template<typename T, typename Y> // 可以把typename 换成 class
int Func(const T& t, const Y& y) {
//TODO
}
答案解析
- C++ 中的 struct 是对 C 中的 struct 进行了扩充,它们在声明时的区别如下:
C | C++ | |
---|---|---|
成员函数 | 不能有 | 可以 |
静态成员 | 不能有 | 可以 |
访问控制 | 默认public,不能修改 | public/private/protected |
继承关系 | 不可以继承 | 可从类或其他结构体继承 |
初始化 | 不能直接初始化数据成员 | 可以 |
- 使用时的区别:C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而C++ 中可以省略 struct 关键字直接使用,例如:
struct Student{
int iAgeNum;
string strName;
}
typedef struct Student Student2; //C中取别名
struct Student stu1; // C 中正常使用
Student2 stu2; // C 中通过取别名的使用
Student stu3; // C++ 中使用
说说include头文件的顺序以及双引号""和尖括号<>的区别
- 区别:
(1)尖括号<>的头文件是系统文件,双引号""的头文件是自定义文件。
(2)编译器预处理阶段查找头文件的路径不一样。 - 查找路径:
(1)使用尖括号<>的头文件的查找路径:编译器设置的头文件路径–>系统变量。
(2)使用双引号""的头文件的查找路径:当前头文件目录–>编译器设置的头文件路径–>系统变量
说说C++结构体和C结构体的区别
区别:
(1)C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。
(2)C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。
(3)C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。 (4)C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而 C++ 中可以省略struct 关键字直接使用。
导入C函数的关键字是什么,C++编译时和C有什么不同?
- 关键字:在C++中,导入C函数的关键字是extern,表达形式为extern “C”, extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
- 编译区别:由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名
答案解析
//extern示例
//在C++程序里边声明该函数,会指示编译器这部分代码按C语言的进行编译
extern "C" int strcmp(const char *s1, const char *s2);
//在C++程序里边声明该函数
extern "C"{
#include <string.h>//string.h里边包含了要调用的C函数的声明
}
//两种不同的语言,有着不同的编译规则,比如一个函数fun,可能C语言编译的时候为_fun,而C++则是
__fun__
简述C++从代码到可执行二进制文件的过程
C++和C语言类似,一个C++程序从源码到执行文件,有四个过程,预编译、编译、汇编、链接
答案解析
- 预编译:这个过程主要的处理操作如下:
(1) 将所有的#define删除,并且展开所有的宏定义
(2) 处理所有的条件预编译指令,如#if、#ifdef
(3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
(4) 过滤所有的注释
(5) 添加行号和文件名标识。 - 编译:这个过程主要的处理操作如下:
(1) 词法分析:将源代码的字符序列分割成一系列的记号。
(2) 语法分析:对记号进行语法分析,产生语法树。
(3) 语义分析:判断表达式是否有意义。
(4) 代码优化:
(5) 目标代码生成:生成汇编代码。
(6) 目标代码优化: - 汇编:这个过程主要是将汇编代码转变成机器可以执行的指令。
- 链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
链接分为静态链接和动态链接。
静态链接,是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。
而动态链接,是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当你删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。
说说static关键字的作用
- 定义全局静态变量和局部静态变量:在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样;
- 定义静态函数:在函数返回类型前加上static关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用;
- 在变量类型前加上static关键字,变量即被定义为静态变量。静态变量只能在本源文件中使用;
//示例
static int a;
static void func();
- 在c++中,static关键字可以用于定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又被隐藏在类的内部。类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。
- 在c++中,static关键字可以用于定义类中的静态成员函数:与静态成员变量类似,类里面同样可以定义静态成员函数。只需要在函数前加上关键字static即可。如静态成员函数也是类的一部分,而不是对象的一部分。所有这些对象的静态数据成员都共享这一块静态存储空间。
答案解析
调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数不属于任何一个对象,因此C++规定静态成员函数没有this指针(划重点,面试题常考)。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行访问。
说说数组和指针的区别
参考回答
- 概念:
(1)数组:数组是用于储存多个相同类型数据的集合。 数组名是首元素的地址。
(2)指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。 指针名指向了内存的首地址。 - 区别:
(1)赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝
(2)存储方式:
数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的,数组的存储空间,不是在静态区就是在栈上。
指针:指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定。
(3)求sizeof:
数组所占存储空间的内存大小:sizeof(数组名)/sizeof(数据类型)
在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。
(4)初始化:
// 数组
int a[5] = { 0 };
char b[] = "Hello"; // 按字符串初始化,大小为6
char c[] = { 'H','e','l','l','o','\0' }; // 按字符初始化
int* arr = new int[10]; // 动态创建一维数组
// 指针
// 指向对象的指针
int* p = new int(0);
delete p;
// 指向数组的指针
int* p1 = new int[10];
delete[] p1;
// 指向类的指针:
string* p2 = new string;
delete p2;
// 指向指针的指针(二级指针)
int** pp = &p;
**pp = 10;
(5)指针操作:
数组名的指针操作
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组
p = a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][] <br>//所以数组指针也称指向一维数组的指针,亦称行指针。|
//访问数组中第i行j列的一个元素,有几种操作方式:
//*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]。其中,优先级:()>[]>*。
//这几种操作方式都是合法的。
指针变量的数据操作:
char *str = "hello,douya!";
str[2] = 'a';
*(str+2) = 'b';
//这两种操作方式都是合法的
说说什么是函数指针,如何定义函数指针,有什么使用场景
参考回答
- 概念:函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指
针所指向的地址。 - 定义形式如下:
int func(int a);
int (*f)(int a);
f = &func;
- 函数指针的应用场景:回调(callback)。我们调用别人提供的 API函数(Application Programming Interface,应用程序编程接口),称为Call;如果别人的库里面调用我们的函数,就叫Callback。
答案解析
//以库函数qsort排序函数为例,它的原型如下:
void qsort(void *base,//void*类型,代表原始数组
size_t nmemb, //第二个是size_t类型,代表数据数量
size_t size, //第三个是size_t类型,代表单个数据占用空间大小
int(*compar)(const void *,const void *)//第四个参数是函数指针
);
//第四个参数告诉qsort,应该使用哪个函数来比较元素,即只要我们告诉qsort比较大小的规则,它就可以
帮我们对任意数据类型的数组进行排序。在库函数qsort调用我们自定义的比较函数,这就是回调的应用。
//示例
int num[100];
int cmp_int(const void* _a , const void* _b){//参数格式固定
int* a = (int*)_a; //强制类型转换
int* b = (int*)_b;
return *a - *b;
}
qsort(num,100,sizeof(num[0]),cmp_int); //回调
说说静态变量什么时候初始化
参考回答
对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。
而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。
答案解析
- 作用域:C++里作用域可分为6种:全局,局部,类,语句,命名空间和文件作用域。
静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。
静态局部变量 :局部作用域,只被初始化一次,直到程序结束。
类静态成员变量:类作用域。 - 所在空间:都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。
- 生命周期:静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。类静态成员变量在静态存储区,当超出类作用域时回收内存
nullptr调用成员函数可以吗?为什么
参考回答
能。
原因:因为在编译时对象就绑定了函数地址,和指针空不空没关系。
答案解析
//给出实例
class animal{
public:
void sleep(){ cout << "animal sleep" << endl; }
void breathe(){ cout << "animal breathe haha" << endl; }
};
class fish :public animal{
public:
void breathe(){ cout << "fish bubble" << endl; }
};
int main(){
animal *pAn=nullptr;
pAn->breathe(); // 输出:animal breathe haha
fish *pFish = nullptr;
pFish->breathe(); // 输出:fish bubble
return 0;
}
原因:因为在编译时对象就绑定了函数地址,和指针空不空没关系。pAn->breathe();编译的时候,函数的地址就和指针pAn绑定了;调用breath(*this)
, this就等于pAn。由于函数中没有需要解引用this的地方,所以函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。
说说什么是野指针,怎么产生的,如何避免
参考回答
- 概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
- 产生原因:释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。这些我们都要注意避免。
- 避免办法:
(1)初始化置NULL
(2)申请内存后判空
(3)指针释放后置NULL
(4)使用智能指针
答案解析
产生原因:释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。这些我们都要注意避免。如:
har *p = (char *)malloc(sizeof(char)*100);
strcpy(p, "Douya");
free(p);//p所指向的内存被释放,但是p所指的地址仍然不变
...
if (p != NULL){//没有起到防错作用
strcpy(p, "hello, Douya!");//出错
}
避免办法:
(1)初始化置NULL
(2)申请内存后判空
(3)指针释放后置NULL
int *p = NULL; //初始化置NULL
p = (int *)malloc(sizeof(int)*n); //申请n个int内存空间
assert(p != NULL); //判空,防错设计
p = (int *) realloc(p, 25);//重新分配内存, p 所指向的内存块会被释放并分配一个新的内存地址
free(p);
p = NULL; //释放后置空
int *p1 = NULL; //初始化置NULL
p1 = (int *)calloc(n, sizeof(int)); //申请n个int内存空间同时初始化为0
assert(p1 != NULL); //判空,防错设计
free(p1);
p1 = NULL; //释放后置空
int *p2 = NULL; //初始化置NULL
p2 = new int[n]; //申请n个int内存空间
assert(p2 != NULL); //判空,防错设计
delete []p2;
p2 = nullptr; //释放后置空