C/C++基础知识
1. static关键字的作用
-
静态局部变量
- 内存中的存储位置:静态存储去.data,这导致未经初始化的会被自动初始化为0
- 作用域:作用域还是局部作用域,区别在于函数或者语句块结束的时候,局部静态变量并没有销毁,简单的说就是当该函数再次被调用的时候其值不变(或者说只初始化一遍,生存周期都是整个程序运行时期)。
-
全局静态变量
- 内存中的存储位置:静态存储去.data,这导致未经初始化的会被自动初始化为0
- 作用域:全局静态变量在声明它的文件之外是不可见的,准确的说从定义之处开始,到文件的结尾。
-
静态函数
- 仅在当前文件内有效。在不同的cpp文件中定义同名函数,不必担心命名冲突。对其它源文件隐藏。在现代C++中被无名namespace取代。
-
类的静态成员变量
- static修饰的数据成员属于类的组成部分,static修饰的数据成员不在栈上分配内存而在.data段分配内存,static修饰的数据成员不能通过调用构造函数来进行初始化,因此static修饰的数据成员必须在类外进行初始化且只会初始化一次。
-
类的静态成员函数
- 不能访问类的私有成员,只能访问类的static成员,不需要类的实例即可调用 《类名》::函数名(参数表) 。
- 属于整个类而非类的对象,没有this指针。
- 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数。非静态成员函数可以任意地访问静态成员函数和静态数据成员。静态成员函数不能访问非静态成员函数和非静态数据成员。
- 静态成员方法可以在类内或类外定义,但必须在类内声明
2. C++和C的区别
- 从设计思想上
- 语法上
- C++具有重载、继承、多态
- C++相比C增加需要类型安全的功能,比如强制类型转换
- C++ 模板类、函数模板
- 个人认为C++就是对C的升级
3. C++中四种cast转换(待补充)
4.C/C++中指针和引用的区别
- 指针就是地址指向一片内存区域,引用是别名
- 使用sizeof 指针大小是4,引用则是被引用对象的大小
- 指针可以被初始化为NULL,而引用初始化必须是一个已有对象
- 指针是间接操作,引用是直接操作,但是都是对对象的直接修改
- 可以有const指针(确保不被修改),但没有const引用
- 指针在使用中可以指向其他对象,但是引用只能是一个对象的引用。
- 指针可以有多级,引用只有一级
- 指针和引用使用++运算符的意义不一样
- 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露
记忆 别名 大小 初始化 间接 const 多级 ++
5. C++中的智能指针
- 智能指针的作用就是管理一个指针,因为存在以下的情况:申请的空间在函数结束时忘记释放,造成内存泄露。使用智能指针很大程度避免这个问题,因为智能指针就是一个类,当超过类的作用域,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是函数结束时自动释放内存空间,不需要手动释放内存空间。
6. 数组和指针的区别
7. 野指针是什么
- 野指针就是指向一个已经被删除的对象或者未申请访问受限内存区域的指针。
8. 为什么析构函数必须是虚函数?为什么c++默认的析构函数不是虚函数
- 将可能被继承的父类的析构函数设置为虚函数,可以保证我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄露(顺序 派生类 成员函数 基类)
- C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。
9. 指针函数和函数指针(定义和初始化)
- 指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
int *fun(int x,int y); - 函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
- 声明格式:类型说明符 (*函数名) (参数)
int (*fun)(int x,int y); - 函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function;
fun = Function;
- 取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
- 调用函数指针的方式也有两种:
x = (*fun)();
x = fun();
int add(int x,int y){
return x+y;
}
int sub(int x,int y){
return x-y;
}
int (*fun)(int x,int y);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
fun = add;
qDebug() << "(*fun)(1,2) = " << (*fun)(1,2) ;
fun = ⊂
qDebug() << "(*fun)(5,3) = " << (*fun)(5,3) << fun(5,3);
return a.exec();
}
10. 析构函数(若有地址)
- 析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
- 析构函数和类名相同只是多一个取反符号~。
- 区别于构造函数它不能带任何参数,也没有返回值(包括void),只能有一个析构函数(可以多个构造),不能重载。
- 系统有默认的析构函数
- 如果类中有指针,并且申请了空间,那么最好在析构函数中销毁
- 执行顺序:派生类-》对象成员-》基类
11. 静态函数和虚函数的区别
- 静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。
12. 重载和重写
- 重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一个作用域中。
- 重写:子类继承父类,父类中的函数是虚函数,在子类中重新定义这个虚函数,这个情况是重写。
13. strcpy和strlen
- strcpy是字符串拷贝函数
char* strcpy(char* dest,const char* src;) src逐个拷贝到dest 遇到\0结束,因为没有指定长度,可能会造成拷贝越界,造成缓冲区溢出,安全版本是strncpy - strlen是计算字符串长度的函数,返回从开始到\0之间的字符个数
14. 静态多态和动态多态
- 多态的实现主要分为
静态多态和动态多态 ,静态多态主要就是重载,在编译的时候就已经确定;动态多态使用虚函数机制实现的。举个例子:一个父类类型的指针指向一个子类对象,使用父类的指针去调用子类重写的父类中的虚函数的时候,就会调用子类重写过后的函数,在父类中声明为virtual关键字的函数,在子类中重写不需要加virtual也是虚函数 - 虚函数的实现:在有虚函数的类中,类的最开始部分就是虚函数表的指针,这个指针指向一个虚函数表,表中放虚函数的地址,实际的虚函数在代码段(.text)。当子类继承父类的时候也会继承其虚函数表,
当子类重写虚函数的时候,会把继承到的虚函数表中的地址替换为重写的函数地址 。使用了虚函数,会增加访问内存开销,降低效率。
15. 内存的几个段,堆,栈
之前专门写过一篇详细的
16. const限定符
- 有时候我们希望定义这样一种变量,它的值不能被改变。为了满足这一要求,可以使用关键字const对变量的类型加以限定。
17. C++函数栈空间的最大值
18. new/delete 和 malloc/free的区别
- new/delete是C++的关键字,而malloc/free是C的库函数, 后者必须指明申请的内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数。
补充说明
malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数 malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。
19.C++中拷贝构造函数的形参能否进行值传递
- 不能,调用拷贝构造函数的是否,首先就是将实参传递给形参,这个传递的时候又要调用拷贝构造函数。如此循环,无法完成拷贝,栈也会满。
20. c语言是如何进行函数调用的(待补充)
21. 结构体计算字节
参考链接 参考链接
- 简单的说就是 offset必须是加入成员有效对齐参数的整数倍 。。。。
22. c++ 结构体 和 class区别
-
在C语言中struct是只能定义数据成员,而不能定义成员函数的。而在C++中,struct类似于class,在其中既可以定义数据成员,又可以定义成员函数。 -
在C++中,struct与class基本是通用的,唯一不同的是如果使用class关键字,类中定义的成员变量或成员函数默认都是private属性的,而采用struct关键字,结构体中定义的成员变量或成员函数默认都是public属性的。 -
在C++中,没有抛弃C语言中的struct关键字,其意义就在于给C语言程序开发人员有一个归属感,并且能让C++编译器兼容以前用C语言开发出来的项目。
23. 数组作为形参 sizeof结果
- 数组作为形参,退化为指针,sizeof(指针)=4,因此不能再被调用函数中利用sizeof(数组名)/sizeof(数组类型)求数组的长度
24. C语言是如何进行函数调用的(待补充)
25. C++如何处理返回值
- 首先,强调一点,和函数传参一样,函数返回时也会做一个拷贝。从某种角度上看,和传参一样,也分为三种:
- 返回值:返回任意类型的数据类型,会将返回数据做一个拷贝(副本)赋值给变量;由于需要拷贝,所以对于复杂对象这种方式效率比较低(调用对象的拷贝构造函数、析构函数);例如:int test(){}或者 Point test(){}
- 返回指针:返回一个指针,也叫指针类型的函数,在返回时只拷贝地址,对于对象不会调用拷贝构造函数和析构函数;例如:int *test(){} 或者 Point *test(){}
- 返回引用:返回一个引用,也叫引用类型的函数,在返回时只拷贝地址,对于对象不会调用拷贝构造函数和析构函数;例如:int &test(){}或者 Point &test(){}
一般来说,在函数内对于存在栈上的局部变量的作用域只在函数内部,在函数返回后,局部变量的内存会自动释放。因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错;但是如果返回的是局部变量的地址(指针)的话,就会造成野指针,程序运行会出错。因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放,这样指针指向的内容就是不可预料,调用就会出错。
26strlen 和 sizeof区别
-
sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。 -
sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是’\0’的字符串。 -
因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。
int main(int argc, char const *argv[]){
const char* str = "name";
sizeof(str);
strlen(str);
return 0;
}
补充
提到sizeof(地址)的值为8,是在64位的编译环境下的,指针的占用大小为8字节;而在32位环境下,指针占用大小为4字节。
27. 堆和栈的区别
- 申请方式不同
- 申请大小限定不同
- 栈顶和栈底是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
- 堆向高地址扩展(向上),是不连续的内存区域,大小可以灵活调整。
- 栈空间默认是4M, 堆区一般是 1G - 4G
- 申请效率不同。
- 栈由系统分配,速度快,不会有碎片。
- 堆由程序员分配,速度慢,且会有碎片。
因为操作系统会在底层对栈提供支持,会分配专门的寄存器存放栈的地址,栈的入栈出栈操作也十分简单,并且有专门的指令执行,所以栈的效率比较高也比较快。
而堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。
28. 虚函数表存放在内存的什么区,虚表指针vptr的初始化时间
- C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。
29. delete和delete[]区别
参考连接
- 简单的说就是
- delete 释放new分配的单个对象指针指向的内存
- delete[] 释放new分配的对象数组指针指向的内存
- 对于像int/char/long/int*/struct等等简单数据类型,由于对象没有destructor,所以用delete 和delete [] 是一样的!但是如果是C++对象数组就不同了!
30. 宏定义和函数有何区别
-
宏在编译之前完成替换(预处理),之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。 -
宏定义属于在结构中插入代码,没有返回值;函数调用具有返回值。 -
宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。 -
宏定义不要在最后加分号。
31. 宏定义和typedef区别
-
宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。 -
宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。 -
宏不检查类型;typedef会检查数据类型。 -
宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。 -
注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。
32. 常量指针和指针常量区别
33. 野指针和悬空指针
- 野指针:指的是没有被初始化过的指针
- 悬浮指针:最初指向的内存已经被释放了的一种指针
int* p;
std::cout<< *p << std::endl;
int * p = nullptr;
int* p2 = new int;
p = p2;
delete p2;
34. 初始化和赋值的区别
class A{
public:
int num1;
int num2;
public:
A(int a=0, int b=0):num1(a),num2(b){};
A(const A& a){};
A& operator=(const A& a){
num1 = a.num1 + 1;
num2 = a.num2 + 1;
return *this;
};
};
int main(){
A a(1,1);
A a1 = a;
A b;
b = a;
return 0;
}
35. 模板函数 函数模板 (作用)
- 模板函数的作用是定义一个通用类型的函数,以便更好的复用。
36. 虚函数和纯虚函数的区别
-
类如果声明了虚函数,这个函数是实现了的,即使是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样编译器就可以使用动态绑定来达到多态的目的(即父类指针指向子类对象,调用子类方法)。而纯虚函数只是在基类中的一个函数定义,即是一个函数声明而已,具体的实现需要留到子类当中。 -
虚函数在子类里面也可以不进行重写(只有虚方法和抽象方法才能够被重写);但纯虚函数必须在子类去实现。 -
虚函数的类用于“实作继承”,也就是说继承接口的同时也继承了父类的实现。当然,子类也可以进行覆写,从而完成自己关于此函数的实现。纯虚函数的类用于“介面继承”,即纯虚函数关注的是接口的统一性,实现由子类去完成。 -
带纯虚函数的类叫做抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。
#include <iostream>
using namespace std;
class Base {
public:
virtual void func() = 0;
};
class child1 : public Base {
public:
void func() { cout << "it's child 1" << endl; }
};
class child2 : public Base {
public:
void func() { cout << "it's child 2" << endl; }
};
int main() {
Base* b = nullptr;
child1 c1;
child2 c2;
b = &c1;
b->func();
b = &c2;
b->func();
return 0;
}
37. 浅拷贝和深拷贝出现的问题
[STL]深拷贝和浅拷贝问题(内存泄露+内存未释放+调用拷贝构造的五种情况)
38. 内联函数和宏定义的区别(待复习)
- 在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。
- 内联函数在编译时直接将函数代码嵌入到目标代码中,省去函数调用的开销来提高执行效率,并且进行参数类型检查,具有返回值,可以实现重载。
- 宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义
- 内联函数有类型检测、语法判断等功能,而宏没有
使用宏定义的地方都可以使用 inline 函数。 作为类成员接口函数来读写类的私有成员或者保护成员,会提高效率。
39. public,protected和private访问和继承权限/public/protected/private的区别?
40. 什么时候会调用拷贝构造函数
- 用类的一个实例化对象去初始化另一个对象的时候
- 函数的参数是类的对象时(非引用传递)
- 函数的返回值是函数体内局部对象的类的对象时 ,此时虽然发生(Named return Value优化)NRV优化,但是由于返回方式是值传递,所以会在返回值的地方调用拷贝构造函数
- 补充一个 push_back
41. C++中NULL和nullptr区别
- NULL在C中就是空指针,但是在C++中void* 类型是不允许隐式转换成其他类型的。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,
因此,建议以后还是都用nullptr替代NULL吧,而NULL就当做0使用 。
42. C++异常处理的方法(待补充)
- try、throw和catch关键字
- 函数的异常声明列表
- C++标准异常类 exception
43. 静态变量什么时候初始化
-
- 初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。
-
- 静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存,但在C和C++中静态局部变量的初始化节点又有点不太一样
。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化 ,在程序运行结束,变量所处的全局内存会被全部回收。
而在C++中,初始化时在执行相关代码时才会进行初始化, 主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的 。
44. 值传递、指针传递、引用传递的区别和效率
-
- 值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构体对象,将耗费一定的时间和空间。(传值)
-
- 指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)
-
- 引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)
-
- 效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。
45. malloc、realloc、calloc的区别
void* malloc(unsigned int num_size);
int *p = (int *)malloc(20*sizeof(int));申请20个int类型的空间;
void* calloc(size_t n,size_t size);
int *p = calloc(20, sizeof(int));
void realloc(void *p, size_t new_size);
类的初始化方式及其区别、效率
class Animal
{
public:
Animal(int weight,int height):
m_weight(weight),
m_height(height)
{
}
Animal(int weight,int height)
{
m_weight = weight;
m_height = height;
}
private:
int m_weight;
int m_height;
};
哪些情况必须使用成员列表初始化
① 当初始化一个引用成员时;
② 当初始化一个常量成员时;
③ 当调用一个基类的构造函数,而它拥有一组参数时;(或者说子类初始化父类的成员)
④ 当调用一个成员类的构造函数,而它拥有一组参数时;(或者说当数据成员是对象的时候)
代码举例
46. 什么事内存泄露,如何检测和避免
补充 区别一下野指针和悬浮指针
47. 析构函数和构造函数执行的顺序
- 构造函数:基类构造函数、对象成员构造函数、派生类本身的构造函数
- 析构函数相反
48. C++函数调用的压栈过程(函数返回地址 函数参数 函数变量 …上个含糊状态.下一个)(待补充)
参考链接
49. 关于this指针你知道什么
参考链接
这个写的也很好
- this指针实质上是一个函数参数,只是编译器隐藏形式的、语法层面上的参数。
class A
{
public:
int func(int p) {};
};
int func(A* const this, int p) {};
-
this指针在成员函数开始前构造,在成员函数结束后清除。 -
this指针并不占用对象的空间 -
this在成员函数开始执行前构造,在成员执行结束后清除。 -
this指针会因编译器不同而有不同的放置位置。可能是堆、栈,也可能是寄存器。C++是一种静态的语言,那么对C++的分析应该从语法层面和实现层面两个方面进行。 -
this指针只有在成员函数中オ有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以通过&this获得),也可以直接使用它。 简单回答如下 -
his指针是类的指针,指向对象的首地址。 -
this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。 -
this指针只有在成员函数中才有定义,且存储位置会因编译器不同有不同存储位置。
50. 构造函数 拷贝构造函数 赋值运算符
构造函数
对象不存在,没用别的对象初始化,在创建一个新的对象时调用构造函数
拷贝构造函数
对象不存在,但是使用别的已经存在的对象来进行初始化
赋值运算符
对象存在,用别的对象给它赋值,这属于重载“=”号运算符的范畴,“=”号两侧的对象都是已存在的
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "我是构造函数" << endl;
}
A(const A& a)
{
cout << "我是拷贝构造函数" << endl;
}
A& operator = (A& a)
{
cout << "我是赋值操作符" << endl;
return *this;
}
~A() {};
};
int main()
{
A a1;
A a2 = a1;
a2 = a1;
return 0;
}
51. C++中临时变量作为返回值的处理过程
参考链接
参考链接
当函数退出时,临时变量出栈,即临时变量已经被销毁,临时变量占用的内存空间没有被清空,但是已经可以被分配给其他变量了,所以有可能在函数退出时,该内存已经被修改了,对于临时变量来说已经是没有意义的值了。 C语言里规定:16bit程序中,返回值保存在ax寄存器中,32bit程序中,返回值保持在eax寄存器中,如果是64bit返回值,edx寄存器保存高32bit,eax寄存器保存低32bit。 - 由此可见,函数调用结束后,返回值被临时存储到寄存器中,并没有放到堆或栈中,也就是说与内存没有关系了。当退出函数的时候,临时变量可能被销毁,但是返回值却被放到寄存器中与临时变量的生命周期没有关系。如果我们需要返回值,一般使用赋值语句就可以了。A a = func();
52. 如何判断两个浮点数是否相等
对两个浮点数判断大小和是否相等不能直接用==来判断,会出错!明明相等的两个数比较反而是不相等!对于两个浮点数比较只能通过相减并与预先设定的精度比较,记得要取绝对值!浮点数与0的比较也应该注意。与浮点数的表示方式有关。
53. 内存对齐的问题(有修正)
1、 分配内存的顺序是按照声明的顺序。
2、 每个变量相对于起始位置的偏移量必须是该变量类型大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止。
3、 最后整个结构体的大小必须是里面变量类型最大值的整数倍。
添加了#pragma pack(n)后规则就变成了下面这样:
1、 偏移量要是n和当前变量大小中较小值的整数倍
2、 整体大小要是n和最大变量大小中较小值的整数倍
3、 n值必须为1,2,4,8…,为其他值时就按照默认的分配规则
54. 如何利用重载比较结构体变量是否相等
struct foo {
int a;
int b;
bool operator==(const foo& rhs) *
{
return( a == rhs.a) && (b == rhs.b);
}
};
- 元素的话,一个个比
- 指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真;
55. 将字符串“hello world”从开始到打印到屏幕上的全过程?(待修正)
之前的博客
1.用户告诉操作系统执行HelloWorld程序(通过键盘输入等)
2.操作系统:找到helloworld程序的相关信息,检查其类型是否是可执行文件;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址。
3.操作系统:创建一个新进程,将HelloWorld可执行文件映射到该进程结构,表示由该进程执行helloworld程序。
4.操作系统:为helloworld程序设置cpu上下文环境,并跳到程序开始处。
5.执行helloworld程序的第一条指令,发生缺页异常
6.操作系统:分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序
7.helloword程序执行puts函数(系统调用),在显示器上写一字符串
8.操作系统:找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程
9.操作系统:控制设备的进程告诉设备的窗口系统,它要显示该字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区
10.视频硬件将像素转换成显示器可接收和一组控制数据信号
11.显示器解释信号,激发液晶屏
12.OK,我们在屏幕上看到了HelloWorld
56. 模板类和模板函数的区别是什么?
57. 模板和实现可不可以不写在一个文件里面?为什么?(重复)
58. 如何在不使用额外空间的情况下,交换两个数?你有几种方法
1) 算术
x = x + y;
y = x - y;
x = x - y;
2) 异或
x = x^y;
y = x^y;
x = x^y;
x ^= y ^= x;
59. 你知道strcpy和memcpy的区别是什么吗?
void *memcpy(void *dest, const void *src, int n);从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中 - 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
- 复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
- 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
60. 程序在执行int main(int argc, char *argv[])时的内存结构,你了解吗?
- 参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针
- char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。
61. 你知道空类的大小是多少吗?
-
C++空类的大小不为0,不同编译器设置不一样,vs设置为1; -
C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址; -
带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定; -
C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。
62. 你什么情况用指针当参数,什么时候用引用,为什么?(待补充)
- 使用引用参数的主要原因有两个:
程序员能修改调用函数中的数据对象
通过传递引用而不是整个数据–对象,可以提高程序的运行速度
- 一般的原则:
对于使用引用的值而不做修改的函数:
如果数据对象很小,如内置数据类型或者小型结构,则按照值传递;
如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针;
如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间;
如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);
- 对于修改函数中数据的函数:
如果数据是内置数据类型,则使用指针
如果数据对象是数组,则只能使用指针
如果数据对象是结构,则使用引用或者指针
如果数据是类对象,则使用引用
63. define宏定义和const的区别
-
编译阶段
- define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用
-
安全性
- define只做替换,不做类型检查和计算,也不求解,容易产生错误,一般最好加上一个大括号包含住全部的内容,要不然很容易出错
const常量有数据类型,编译器可以对其进行类型安全检查 -
内存占用
- define只是将宏名称进行替换,在内存中会产生多分相同的备份。const在程序运行中只有一份备份,且可以执行常量折叠,能将复杂的的表达式计算出结果放入常量表
64. C和C++类型安全(待补充)
65. volatile、mutable和explicit关键字的用法(待补充)
66. const关键字的作用
-
- 阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
-
对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const ,或二者同时指定为const; -
- 在一个函数声明中
,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值 ; -
- 对于类的成员函数,若指定其为const类型
,则表明其是一个常函数,不能修改类的成员变量,类的常对象只能访问类的常成员函数 ; -
- 对于类的成员函数,
有时候必须指定其返回值为const类型,以使得其返回值不为“左值” 。 -
- const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;
-
- 非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;
-
- 一个没有明确声明为const的成员函数被看作是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。因此const对象只能调用const成员函数。
-
- const类型变量可以通过
类型转换符const_cast 将const类型转换为非const类型; -
- const类型变量必须定义的时候进行初始化,因此也导致如果类的成员变量有const类型的变量,那么该变量必须在类的初始化列表中进行初始化;
-
- 对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参。则这个时候无论加不加
const对实参不会产生任何影响。但是在引用或指针传递函数调用中,因为传进去的是一个引用或指针,这样函数内部可以改变引用或指针所指向的变量,这时const 才是实实在在地保护了实参所指向的变量。 因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
67. 什么是类的继承
-
- 类与类之间的关系
-
- 继承的相关概念
- 所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类;
-
- 继承的特点
- 子类拥有父类的所有属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象使用;
-
- 继承中的访问控制
-
- 继承中的构造和析构函数
-
- 继承中的兼容性原则
68. new和delete的实现原理,delete如何知道释放内存的的大小
- 对于简单类型 直接调用operator new分配内存。而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数
- 对于简单类型,new[]计算好大小后调用operator new,对于复杂数据结构,
new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n ,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小;
- new表达式调用一个名为operator new(operator new[])函数,分配一块足够大的、原始的、未命名的内存空间;
- 编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
- 对象被分配了空间并构造完成,返回一个指向该对象的指针。
- delete简单数据类型默认只是调用free函数;复杂数据类型先调用析构函数再调用operator delete;
针对简单类型,delete和delete[]等同 。假设指针p指向new[]分配的内存。因为要4字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址。delete[]实际释放的就是p-4指向的内存。而delete会直接释放p指向的内存,这个内存根本没有被系统记录,所以会崩溃。 需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。
所以我们常常简单的说 使用new 对应delete new【】 对应使用delete【】
69. malloc申请的存储空间能用delete释放吗
- 不能,malloc /free(库函数)主要为了兼容C,new和delete(运算符 关键字) 完全可以取代malloc /free的。
- malloc /free的操作对象都是必须明确大小的,而且不能用在动态类上。
- new 和delete会自动进行类型检查和大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。
- 当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。
70. 拷贝构造函数和赋值构造函数的区别
Student s;
Student s1 = s;
Student s2;
s2 = s;
补充:平时常用:类中有指针变量时要重写析构函数、拷贝构造函数和赋值运算符
71. C++11有哪些新特性
- nullptr替代 NULL
- 引入了 auto 和 decltype 这两个关键字实现了类型推导
- 基于范围的 for 循环for(auto& i : res){}
- 类和结构体的中初始化列表
- Lambda 表达式(匿名函数)
- std::forward_list(单向链表)
- 右值引用和move语义
72. C++中的指针参数传递和引用参数传递有什么区别?底层原理你知道吗?
1) 指针参数传递本质上是值传递,它所传递的是一个地址值。
值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本 (替身)。
值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变 )。
2) 引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。
被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。
因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
3) 引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。
而对于指针传递的参数,如果改变被调函数中的指针地址,它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。
4) 从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。
指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。
符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。
73. define 和 typedef 和 inline
-
define和别名typedef的区别
-
- 执行时间不同,typedef在编译阶段有效,typedef有类型检查的功能;#define是宏定义,发生在预处理阶段,不进行类型检查;
-
- 功能差异,typedef用来定义类型的别名,定义与平台无关的数据类型,与struct的结合使用等。#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
-
- 作用域不同,#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。
-
define与inline的区别
-
- #define是关键字,inline是函数;
-
- 宏定义在预处理阶段进行文本替换,inline函数在编译阶段进行替换;
-
- inline函数有类型检查,相比宏定义比较安全;
74. cout 和 printf的区别
- cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。
cout < < "abc " < <endl;
或cout < < "abc\n ";cout < <flush; 这两个才是一样的.
- flush立即强迫缓冲输出。 printf是无缓冲输出。有输出时立即输出
75. 静态成员和普通成员的区别是什么
- 生命周期
静态成员变量从类被加载开始到类被卸载,一直存在;
普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;
- 共享方式
静态成员变量是全类共享;普通成员变量是每个对象单独享用的;
- 定义位置
普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;
- 初始化位置
普通成员变量在类中初始化;静态成员变量在类外初始化;
- 默认实参
可以使用静态成员变量作为默认实参,
76. ifdef 和 endif代表着什么
参考链接
- 一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
- 条件编译命令最常见的形式为:
\#ifdef 标识符
程序段1
\#else
程序段2
\#endif
- 它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。 其中#else部分也可以没有,即:
在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量“重定义”错误。在头文件中使用#define、#ifndef、#ifdef、#endif能避免头文件重定义。 待补充
- 当程序中有函数重载时,函数的匹配原则和顺序是什么?
-
名字查找 -
确定候选函数 -
寻找最佳匹配
|