C++八股文--基础详解
目录
7. 关键字define、const、typedef、inline
1. 智能指针
优点
(1)避免内存泄漏。底层维护一个类,作用域结束可以自动释放内存。
(2)共享指针便于传播和释放。例如多线程使用同一个对象时析构问题。
auto_ptr
(c++98用,c++11已放弃)。如下,p2剥夺了p1所有权,访问p1会报错。
auto_ptr<std::string> p1 (new string ("hello"));
auto_ptr<std::string> p2;
p2 = p1;
unique_ptr
独占所有权的指针,即同一时间只能有一个std::unique_ptr指向同一个对象。如下,P4 = P3会报错,因为P3不能再指向有效数据。
unique_ptr<string> p3 (new string (auto));
unique_ptr<string> p4;
p4 = p3;
shared_ptr
共享所有权的指针,即可以有多个std::shared_ptr指向同一个对象。有两个指针,一个指向堆内存中的对象,另一个指向共享实例的引用计数器。
引用计数器
- 每次调用shared_ptr的复制构造函数时,引用计数器+1。
- 每次调用shared_ptr的赋值运算符时,右侧指针计数器+1,左侧指针计数器-1。
- 每次调用shared_ptr的析构函数时,计数器均-1。
- 计数器为0时,删除该对象。
weak_ptr
弱引用指针,即一个std::weak_ptr指向一个std::shared_ptr所管理的对象,但不会增加引用计数。
2. 内存分配
栈:存放局部变量和函数参数,编译器管理内存分配和回收。
堆:程序员管理,手动new/delete或malloc/free,容易造成内存泄漏。
全局、静态存储区:存储初始化/未初始化的全局变量和静态变量,有初始化和未初始化两个区域。
代码区:存放二进制代码。
一维数组动态分配释放代码示例:
// 动态分配,数组长度为 m
int *array=new int [m];
//释放内存
delete [] array;
二维数组动态分配释放代码示例:
int **array;
array = new int *[m];
for(int i = 0; i < m; i++ ) {
array[i] = new int [n];
}
//释放
for(int i = 0; i < m; i++ ) {
delete [] array[i];
}
delete [] array;
对象动态分配释放代码示例:
#include <iostream>
using namespace std;
class Box {
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray;
return 0;
}
3. 指针参数传递和引用参数传递
指针参数传递
本质是值传递,传递的是一个地址值,传递过程中,被调函数的形参作为被调函数的局部变量处理,在栈开辟空间存放有主调函数传递来的实参,从而形成实参副本。特点:被调函数对形参指针改变不影响主函数实参指针。
引用参数传递
被调函数形参也作为局部变量在栈中开辟了内存空间,存放的是主调函数放进来的实参变量的地址。特点:被调函数对形参指针改变会被处理成间接寻址,即通过栈中存储的地址找到实参,因此,被调函数对形参指针改变会影响主函数实参指针。
4. 关键字static
修饰局部变量
局部变量一般存储于栈,static修饰的局部变量存储于静态数据区,生命周期会一直延续到整个程序结束,作用域未变还是在其语句块。
修饰全局变量
static修饰的全局变量仅在本文件可见。
修饰函数
static修饰函数,函数仅在本文件可见。
修饰类
static修饰类成员变量,则对应成员变量属于整个类而不是对象,没有this指针(this指向本对象),需要在类外初始化,供所有对象共享节省内存,并且只能访问static修饰的类成员。
static修饰类成员方法,则对应成员方法属于整个类而不是对象,没有this指针,并且只能访问静态成员。
5. 关键字const
修饰基本数据类型:使用过程中不可改变常量值
修饰指针变量:const int* p1,指向整形常量。const int* const p3,指向整形常量的常量指针。
修饰引用变量:int* const p2,指向整形的常量指针。
修饰函数:
const int& function(int& x); //修饰返回值,返回值是引用,不能被修改
int& function(const int& x); //修饰形参
int& function(int& x) const{} //const成员函数,保证对象成员不被修改,可以访问非const和const的数据成员和变量,可以被重载; 如果成员是指针,则不能保证对象成员不被修改
7. 关键字define、const、typedef、inline
define:编译阶段处理,无类型,无类型检查,遇到就展开。
const:编译期间处理,有类型,有类型检查。const常量在出现的地方保留真正的数据,编译器有时不为const常量分配内存,直接将const常量添加到表中,省去读写内存,效率更高。
typedef:定义类型别名;与struct结合,定义类型;编译阶段有效,有类型检查;
inline:内联函数,调用时进行替换;有类型检查;
8. 重载、重写和重定义
重载:overload,函数同名,参数列表不同。
重写:override,子类重写父类中的虚函数(同名,同参数列表,同返回等)。
重定义:子类重新定义父类中同名非虚函数,可修改参数列表。
9. 强制转换
static_cast:转换类型明确;无动态类型检查;派生类转基类安全,所以主要适用于非多态。
dynamic_cast:动态派生类间转换;type id必须是类指针、类引用或void*;转换安全,类型不一致转换为空指针。
const_cast:专用于const类型去除或增加;
reinterpret_cast:整形转指针引用;指针转数组;指针引用间转换;指针引用转整形。
10. 指针和引用
指针是一个实体,引用是一个别名。
指针:独立的变量;有const;可以有多级;可为空;存放地址;编译时添加“指针变量名-指针变量地址”,允许拷贝和赋值;sizeof得到指针类型大小。
引用:依赖原变量的内存别名;无const;只有1级;编译时,将“引用变量名-引用对象地址”添加到符号表,之后从一而终,不能引用其他数据;sizeof得到所引用对象大小。
11. 野指针和悬空指针
野指针:没被初始化的指针。
悬空指针:指针指向的内存被回收,指针仍然指向这块内存。
12. 构造函数
创建类的对象时,编译器会为对象分配内存空间,自动调用构造函数初始化对象成员。
默认构造函数:一个类没有构造函数,生成默认构造函数条件为:有一个类类型成员变量,继承自含有默认构造函数的基类,继承或声明了虚函数,含有虚基类。
普通构造函数:创建新对象,初始化成员。
拷贝构造函数:用一个对象去初始化同类的另一个对象;函数F的参数是类A的对象,F被调用时候;函数F的参数返回值是类A的对象,F返回时。
转换构造函数:一个构造函数接受一个不同于其类类型的形参,可视为将形参转换成类的一个对象
移动构造函数:将其它对象拥有的资源内存移位己用。
赋值运算符重载:=右边的本类对象的值赋值给=左边的对象。
A a1, A a2; a1 = a2; // 赋值运算符重载
A a3 = a1; // 拷贝构造函数
13. 堆和栈
堆:程序员手动malloc和free或new和delete,如果不及时回收会造成内存泄漏;空间不连续;低地址向高地址扩展;空间较大较灵活;
栈:需要时编译器分配,不需要时自动回收;连续的存储空间;高地址向低地址扩展;空间较小。
14. 函数传参
值传递:形参是实参的拷贝。
指针传递:值传递的一种,形参指向实参地址。
引用传递:引用对象的地址放在栈空间。形参任何操作都可以直接映射到外部实参。
15. malloc/free和new/delete
区别:new/delete是操作符,可执行构造函数和析构函数;malloc/free是库函数,不在编译器控制权限之内。
new:分配未初始化的空间;用对象的构造函数对空间初始化。
delete:使用析构函数对对象析构;回收内存空间。
malloc:分配空间。
free:释放空间。
16. volatile和extern
volatile:易变性,下一条语句不回去用上一条语句里volatile变量的寄存器内容;不可优化型,不可优化和删除;顺序性,编译器不会进行重排乱序优化。
extern:C加此声明,表示此变量、函数在别处定义,在此引用;C++中除此之外还有一个规范性作用,即C++调用C库函数需要“extern C”。
17. 类大小
class A{}; sizeof(A) = 1;
class A{virtual Fun(){} }; sizeof(A) = 4(32bit)/8(64bit); // 指向虚函数表
class A{static int a; }; sizeof(A) = 1;
class A{int a; }; sizeof(A) = 4;
class A{static int a; int b; }; sizeof(A) = 4;
18. 结构体内存对齐
结构体第一个成员偏移为0;之后每个成员偏移min(#pragma pack()指定数,数据成员本身长度);结构体本身也要进行对齐。
对齐原因:CPU内存访问速度提升;减少额外操作,举例开始存储1字节,读取0-3,读取4,删除0;CPU有时候拒绝处理未内存对齐的数据。
19. 内存泄漏
申请一块内存空间,使用完毕后未释放,但是指向此空间的指针已被回收,长期以往,这样的空间越来越多,再申请新空间空间不够时,系统崩溃。
20. 预处理、编译、汇编、链接
预处理:删除#define,展开宏定义,处理所有条件编译指令,处理所有预编译指令,删除所有注释,添加行号和文件名标识。
编译:源码转换为机器语言,转化为相应的汇编文件。静态编译:编译时候将对应链接库中的函数链接到可执行文件。动态链接:执行时,调用对应的动态链接库的命令。
汇编:调用汇编器将汇编代码转换为机器可执行的指令。
链接:将所有.o和库链接在一起,得到可执行文件。
21. 命名空间
命名空间定义:作为附加信息区分不同库中相同名称的函数、类、变量等。
代码示例:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func() {
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func() {
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main () {
func();
return 0;
}
22. 模版
模板:创建泛型编程类或函数。
代码示例:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b) {
return a < b ? b:a;
}
int main () {
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
return 0;
}
类模板代码示例:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem) {
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop () {
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
elems.pop_back();
}
template <class T>
T Stack<T>::top () const {
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素
return elems.back();
}
int main() {
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}