前言
通过学习编程指北中的内容加强C++的基本概念的认识,并且了解面试中常见的考察重点。通过此篇文章记录学习过程,加深印象,后续可能还会补充修改。
一、C++基础概念
0.基本知识
一个程序还需要经过以下几个主要的过程,按照顺序分别是:
- 预处理(Preprocessing):预处理器会对代码进行处理,包括宏展开、头文件包含等操作。预处理器会根据以 # 开头的指令进行处理,生成一份预处理后的代码。
- 编译(Compilation):编译器将预处理后的代码翻译成汇编语言或机器语言的过程。编译器会对代码进行语法分析、语义分析、代码优化等操作,生成目标代码或可执行文件。
- 链接(Linking):链接器将多个目标文件合并成一个可执行文件的过程。链接器会将代码中使用到的库函数和其他目标文件中的符号链接起来,生成可执行文件。
- 运行(Execution):可执行文件被操作系统加载到内存中运行。程序的执行过程中,CPU 会按照程序的指令序列执行代码,程序会占用计算机的内存、CPU、I/O 设备等资源。
需要注意的是,不同的编程语言和开发环境可能会有不同的具体实现方式,但是大多数程序的执行过程都包含了以上这些基本过程。
常见函数合集
swap(a,b);//a,b可以是指针
strcpy(a,b);//C中的库,C++中可以使用#include string (符号打不出来,算了)更灵活
to_string();//to_string 是一个字符串转换函数,它可以将不同类型的变量转换成对应的字符串类型
pop_back();// 是 vector 和 string 容器的成员函数,用于从容器的尾部移除一个元素
pop();//是 stack 和 queue 容器的成员函数,用于从容器的顶部移除一个元素
1. sizeof() 和 strlen() 的使用
int func(char array[]) {
printf("sizeof=%d\n", sizeof(array));
printf("strlen=%d\n", strlen(array));
}
int main() {
char array[] = "Hello World";
printf("sizeof=%d\n", sizeof(array));
printf("strlen=%d\n", strlen(array));
func(array);
}
作者: 编程指北
链接: https://csguide.cn/cpp/basics/array_and_pointer.html#sizeof-%E6%95%B0%E7%BB%84%E5%8F%82%E6%95%B0
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
输出的结果为
sizeof=12
strlen=11
sizeof=8
strlen=11
数组退化:当数组作为函数参数传递时,通常会自动退化为指向数组首元素的指针。这是由于数组在内存中是连续存储的,因此传递数组时只需要传递数组的起始地址即可。
如何防止数组退化?
1.将数组的长度作为额外的参数传递给函数。
void processArray(int* arr, int length) {
// 在函数内部使用数组长度
for (int i = 0; i < length; i++) {
// 处理数组元素
}
}
int main() {
int array[] = {1, 2, 3, 4, 5};
int length = sizeof(array) / sizeof(array[0]);
processArray(array, length);
return 0;
}
2.使用标准库容器:使用标准库提供的容器类(如 std::vector)可以方便地传递和处理动态大小的数组,而不会发生退化的问题。
#inclde <iostream>//用于包含输入输出流的头文件,其中定义了 std::cout 和 std::cin 等用于标准输入输出的对象。
#include <vector>
using namespace std;
void processVector(const vector<int>& vec) {
// 在函数内部使用容器的大小
for (int i = 0; i < vec.size(); i++) {
// 处理容器元素
}
}
int main() {
vector<int> vec = {1, 2, 3, 4, 5};
processVector(vec);
return 0;
}
2. const关键字
| 修饰对象 | 案例说明 |
|---|---|
| 常量 | const int a = 10; |
| 常量引用 | const int a = 10; const int& b = a; // 声明一个常量引用,引用常量 a |
| 函数参数 | void func(const int a){} |
| 函数返回值 | const int func(){} |
| 常量指针 | 如果 const后面跟的是指针,就不能做 *p=20 操作,即不能修改指针指向的值,指针可以指向新的变量 |
| 指针常量 | 如果const 后面跟的常量,就不能做 p = &b 操作,即不能修改指针的指向 |
| 结构体 | const int a = 10;禁止对结构体进行修改,防止误触 |
看const右侧紧跟着的是指针还是常量,是指针就是常量指针,是常量就是指针常量。
const int* p = &a; // const(常量) *(指针) → 常量指针
举例
const int* p; // 声明一个指向只读变量的指针,可以指向 int 类型的只读变量
int a = 10;
const int b = 20;
p = &a; // 合法,指针可以指向普通变量
p = &b; // 合法,指针可以指向只读变量
*p = 30; // 非法,无法通过指针修改只读变量的值
作者: 编程指北
链接: https://csguide.cn/cpp/basics/const.html#_4-%E4%BF%AE%E9%A5%B0%E6%8C%87%E9%92%88%E6%88%96%E5%BC%95%E7%94%A8
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
int* const p2 = &a; // *(指针)const(常量) → 指针常量
举例
int a = 10;
int b = 20;
int* const p = &a; // 声明一个只读指针,指向 a
*p = 30; // 合法,可以通过指针修改 a 的值
p = &b; // 非法,无法修改只读指针的值
作者: 编程指北
链接: https://csguide.cn/cpp/basics/const.html#_4-%E4%BF%AE%E9%A5%B0%E6%8C%87%E9%92%88%E6%88%96%E5%BC%95%E7%94%A8
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3. 野指针
在C++中,判断一个指针是否是野指针(悬空指针)可以通过以下几种方法:
1.初始化为nullptr或NULL:在定义指针变量时,可以将其初始化为nullptr(C++11及以上)或NULL(C++98/C++03)表示空指针。
int* ptr = nullptr; // C++11及以上
int* ptr = NULL; // C++98/C++03
2.初始化为有效地址:在定义指针变量时,可以将其初始化为有效的内存地址。有效地址是指指针指向已分配内存的位置。
int value = 42;
int* ptr = &value;
3.使用动态内存分配时,注意释放内存:如果使用了动态内存分配(如new操作符),则需要确保在不再使用指针时及时释放内存。如果指针未释放而被重用,则可能导致野指针的问题。
int* ptr = new int;
// 使用 ptr
delete ptr; // 释放内存
ptr = nullptr;
4.值传递(Pass by Value)与地址传递(Pass by Address)----指针的应用
#include <iostream>
using namespace std;
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
//指针和函数
int a = 10;
int b = 20;
swap(&a, &b);
//如果是地址传递,可以修改实参,原来 a = 10;b = 20,地址传递后 a = 20,b = 10.
cout << "a =" << a << endl;
cout << "b =" << b << endl;
//如果是值传递,不可以修改实参,原来 a = 10;b = 20,值传递后 a = 10,b = 20.
system("pause");
return 0;
}
来源:黑马程序员课程分享
5. static关键字
| 修饰范围 | 作用效果 |
|---|---|
| 全局变量 | 只有当前文件范围内可以访问 |
| 局部变量 | 只有当前函数内部可以访问 |
| 函数 | 只有当前文件可以调用 |
| 类成员变量和函数 | 在所有类对象之间共享,且不需要创建对象就可以直接访问 |
6.字节对齐是什么意思,为什么需要对齐?
7.C++中class和struct的区别

摘自编程指北
8.宏定义与内联函数、typedef
宏定义:定义常量或者宏函数
1.使用预处理器指令 #define 定义,在编译期间将宏展开,并替换宏定义中的代码。预处理器只进行简单的文本替换,不涉及类型检查。
2.宏定义没有作用域限制,只要在宏定义之后的地方,就可以使用宏。
3.不能用于定义模板作为别名。
#define INT_VECTOR std::vector<int>
内联函数:只需在函数声明前加上 inline 关键字即可。
具体例子可以戳这里
typedef :是一种类型定义关键字,用于为现有类型创建新的名称(别名)。
1.是在编译阶段处理的,有更严格的类型检查。
2.typedef 可以与模板结合使用,但在 C++11 之后,推荐使用 using 关键字定义模板类型别名。
typedef std::vector<int> IntVector;
// 使用 using 定义模板类型别名(C++11 及以后)
template <typename T>
struct MyContainer {
using Type = std::vector<T>;
};
作者: 编程指北
链接: https://csguide.cn/cpp/basics/define_and_typedef.html#%E5%8C%BA%E5%88%AB
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
C/C++ 中变量的声明和定义是两个不同的概念。 声明是指告诉编译器某个符号的存在,在程序变量表中记录类型和名字,而定义则是指为该符号分配内存空间或实现其代码逻辑。
9.mutable 关键字
没细看
10.强制类型转换
还没细看
int a = 42;
double b = static_cast<double>(a); // 将整数a转换为双精度浮点数b 显示转换
作者: 编程指北
链接: https://csguide.cn/cpp/basics/type_conversions.html#%E4%B8%80%E3%80%81static-cast
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
11.面向对象三大特性
封装:class (属性:成员变量+方法:成员函数)
继承:基类和派生类,考察点:访问修饰符(如 public、protected、private,要清楚各自的应用场景)、派生类对基类成员的访问权限
多态:虚函数和抽象类
12.重载、重写、隐藏的区别
重载:方法名相同,但是参数和返回类型可能不同,可以结合实际需求调用相应的模式(扩展功能,所以参数、返回类型可以不同)
重写:在派生类中重新定义基类,相当于对方法对应的功能的扩展。除了必要的重写语法格式,改写的方法其方法名、参数、返回类型必须相同(重写嘛,所以肯定外表相同但内里改变)
隐藏:让派生类中的方法覆盖基类中的方法,实现一些特殊需求。(只要函数名相同,其他无所谓,基类会被隐藏)
重写的举例:
#include <iostream>
class BaseClass {
public:
virtual void display() {
std::cout << "Display method in base class" << std::endl;
}
};
class DerivedClass : public BaseClass {
public:
void display() override {
std::cout << "Display method in derived class" << std::endl;
}
};
int main() {
DerivedClass derived;
derived.display();
return 0;
}
作者: 编程指北
链接: https://csguide.cn/cpp/object_oriented/overloading_overriding_and_hiding.html#%E4%BA%8C%E3%80%81%E9%87%8D%E5%86%99-overriding
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
隐藏的举例:
#include<iostream>
using namespace std;
classA{
public:
void fun1(int i, int j){
cout <<"A::fun1() : " << i <<" " << j << endl;
}
};
classB : public A{
public:
//隐藏
void fun1(double i){
cout <<"B::fun1() : " << i << endl;
}
};
int main(){
B b;
b.fun1(5);//调用B类中的函数
b.fun1(1, 2);//出错,因为基类函数被隐藏
system("pause");
return 0;
}
作者: 编程指北
链接: https://csguide.cn/cpp/object_oriented/overloading_overriding_and_hiding.html#%E4%BA%8C%E3%80%81%E9%87%8D%E5%86%99-overriding
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我觉得编程指北讲得挺好的,直接截图了
13.程序运行过程中的一些执行顺序
初始化:虚基类(虚中按照声明分先后)>普通类(类中按照声明分先后)>成员初始化(按照声明顺序分先后)>执行构造函数
与之相对应的,析构的过程中,与上述顺序形成镜像对称
此外,有构造必定有析构
举例说明:
Base3 constructor
Base2 constructor
Base1 constructor
Base constructor
MyClass constructor
MyClass destructor
Base destructor
Base1 destructor
Base2 destructor
Base3 destructor
作者: 编程指北
链接: https://csguide.cn/cpp/object_oriented/initialization_and_destruction_order.html#%E7%B1%BB%E7%9A%84%E6%9E%90%E6%9E%84%E9%A1%BA%E5%BA%8F
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
14.深拷贝与浅拷贝
浅拷贝:它仅复制对象的基本类型成员和指针成员的值,而不复制指针所指向的内存。新对象和原对象会共享同一个资源。当其中一个对象释放资源时,另一个对象也会受到影响。
深拷贝:不仅复制对象的基本类型成员和指针成员的值,还复制指针所指向的内存。为新对象分配一块新的内存,然后将原对象的数据复制到新的内存中。这样新对象和原对象拥有各自独立的资源,并且修改一个对象的资源不会影响另一个对象。
15.多态的具体实现
基本定义:
多态(Polymorphism)是 C++ 中的一个重要的面向对象编程特性,它允许不同的对象使用相同的接口来执行不同的操作。多态性是面向对象编程的核心之一,可以提高程序的可读性、可维护性和可扩展性,同时也是实现运行时多态的基础。
虚函数、纯虚函数实现的多态叫动态多态,模板函数、重载等实现的叫静态多态。区分静态多态和动态多态的一个方法就是所调用的具体方法是在编译期还是运行时,运行时就叫动态多态。
PS:到这里需要做一点理论概念上的梳理,从编程语言语法的实现角度来说,我们会称之为存在虚函数、纯虚函数、模板函数和函数重载。从面向对象的三大特性这个角度来说,我们称之为多态。通过编程语言中的不同语法功能实现了多种操作特性,应用于实际的产品工作中。所以,不要看到代码后在怀疑这到底是重载还是多态,事实上你没有理清楚概念划分的层次。
多态满足的条件:
1.有继承关系
2.子类重写父类中的虚函数
多态使用的条件:
父类指针或引用指向子类对象
举例说明:
class Shape {
public:
virtual int area() = 0;
};
class Rectangle: public Shape {
public:
int area () {
cout << "Rectangle class area :";
return (width * height);
}
};
class Triangle: public Shape{
public:
int area () {
cout << "Triangle class area :";
return (width * height / 2);
}
};
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
shape = &rec;
shape->area();
shape = &tri;
shape->area();
return 0;
}
作者: 编程指北
链接: https://csguide.cn/cpp/object_oriented/polymorphism_in_cplusplus.html#%E8%99%9A%E5%87%BD%E6%95%B0%E3%80%81%E7%BA%AF%E8%99%9A%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%A4%9A%E6%80%81
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
16.this指针
在 C++ 中,this 是一个指向当前对象的指针,它是一个隐式参数,与Python中的self很相似,可以在成员函数中使用。每个对象都有自己的 this 指针,它指向对象本身的地址。使用 this 指针可以方便地访问对象的成员变量和成员函数,以及在成员函数中返回对当前对象的引用。
具体的用法:
1.在成员函数中访问对象的成员变量和成员函数
2.在成员函数中返回对当前对象的引用
class Point {
public:
Point(int x, int y) : m_x(x), m_y(y) {}
Point& move(int dx, int dy) {
m_x += dx;
m_y += dy;
return *this;
//move 函数通过 this 指针来返回对当前对象的引用,即 *this,可以实现链式调用。
}
void print() const { std::cout << "(" << m_x << ", " << m_y << ")" << std::endl; }
private:
int m_x;
int m_y;
};
int main() {
Point p(1, 2);
p.move(3, 4).move(5, 6);
p.print(); // 输出 "(9, 12)"
return 0;
}
注意事项:

截图来源:编程指北,戳这里
17.虚函数与虚函数表
问答题:虚函数定义?
1.虚函数是一种特殊的成员函数,用于实现运行时多态性(Runtime Polymorphism)。通过将函数声明为虚函数,可以在父类和子类之间实现多态行为,即在运行时根据对象的实际类型来确定调用的函数。
2.一个类中如果包含了虚函数,那么在使用该类创建的对象时,编译器会在对象的内存中分配一个虚函数表(v-table),该表中存储了虚函数的地址。当程序调用一个虚函数时,实际上是通过对象的虚函数表来查找该函数的地址,并进行调用。由于虚函数表是在运行时才确定的,因此可以实现多态性。
PS:所以,在这里重载和重写的区别就显得更加清晰了,重载是对相同的函数功能设置不同的参数、参数顺序、返回类型,而重写是派生类对基类中的虚函数进行重新设计以满足自身的功能需求。重写的函数具有相同的函数名、参数列表和返回类型,但是它们的实现不同。
重载
class Calculator {
public:
int add(int x, int y) { return x + y; }
double add(double x, double y) { return x + y; }
int add(int x, int y, int z) { return x + y + z; }
};
重写
class Shape {//基类
public:
virtual double getArea() { return 0; }//这种没有实际功能,只是定义了一个函数名的存在于基类中的存在叫做纯虚函数
};
class Rectangle : public Shape {//派生类首先完成继承,然后进行改写
public:
double getArea() override { return width * height; }
private:
double width;
double height;
};
通过基类的指针或引用访问派生类的成员。

动态多态的底层实现原理:介绍虚函数与虚函数表的关系,以及在运行时的调用过程和语法要求,这里已经写得很清楚了,不需要多啰嗦转化,直接戳开看
问答题:动态多态的底层实现原理?
主要涉及到虚函数和虚函数表
在 C++ 中,如果一个成员函数被声明为虚函数,那么在对象的内存布局中会包含一个指向虚函数表(v-table)的指针。虚函数表是一个数组,存储了该类中所有虚函数的地址,每个虚函数在表中的位置与其在类中声明的顺序一致。在使用虚函数时,程序会通过该对象的虚函数表,查找对应函数的地址并进行调用。
当一个类被继承时,子类会继承父类的虚函数表,并且在表中添加自己的虚函数。如果子类重写了父类中的虚函数,那么子类中的虚函数会覆盖父类中的虚函数,并在表中占据相应的位置。因此,当一个虚函数被子类重写时,程序会根据对象的实际类型在其对应的虚函数表中查找该函数的地址,并进行调用。
问答题:动态多态的实现的意义?
1.增强代码的灵活性:通过使用动态多态,程序可以在运行时根据对象的实际类型来确定函数的具体实现,从而增强了代码的灵活性和可扩展性。如果程序需要在未来添加新的子类或修改已有的子类,只需要修改子类的实现代码,而不需要修改父类的代码,从而降低了代码的耦合性。
2.实现代码的复用:通过将一些通用的代码放在父类中,可以将这些代码的实现继承给子类,从而实现代码的复用。子类可以继承父类的成员变量、成员函数和虚函数,从而避免了重复编写相同的代码。
3.简化代码的设计:通过使用动态多态,程序可以将一组具有相似行为的对象抽象为一个父类,从而简化了代码的设计。父类可以定义一些通用的行为和属性,而子类可以根据自己的特性重写或增加这些行为和属性,从而实现代码的多样性。
4.实现运行时多态:动态多态的实现可以在运行时根据对象的实际类型确定函数的具体实现,从而实现运行时多态。这种多态性可以帮助程序更好地适应复杂的运行时环境,从而提高程序的可靠性和稳定性。
纯虚函数的使用
这里面写得清清楚楚
总而言之,基类中没有实际功能的虚函数称为纯虚函数,相当于一个抽象类,可以作为接口,便于接下从同一个接口实现不同的功能。它无法实例化,也无法创建对象。
18.关于构造函数的一点补充
#include <iostream>
using namespace std;
class MyClass {
public:
int x, y;
// 构造函数
MyClass(int a, int b) {
x = a;
y = b;
}
void print() {
cout << "x = " << x << ", y = " << y << endl;
}
};
int main() {
MyClass obj(10, 20); // 创建 MyClass 对象,并传入参数
obj.print(); // 输出对象的成员变量
return 0;
}
在上面的示例中,MyClass 类包含一个构造函数,该构造函数接受两个整数参数,并将它们分别赋值给类的成员变量 x 和 y。在 main() 函数中,我们使用 MyClass 类的构造函数创建了一个对象 obj,并传入了参数 10 和 20。然后,我们调用对象的成员函数 print(),输出对象的成员变量 x 和 y。
C++ 中的构造函数的名称必须与类的名称相同,并且不能有返回值。如果没有显式定义构造函数,编译器会自动生成一个默认的构造函数,其中所有的成员变量都会被默认初始化。如果我们需要为类提供不同的初始化方式,则可以定义多个构造函数,并使用不同的参数列表来重载它们。
在C++中,可以使用类内的构造函数来创建对象的实例,这种方式被称为“默认构造函数”。默认构造函数可以在类定义中定义,它们没有参数,也没有返回值,用于创建对象的默认实例。使用类内的构造函数创建对象的实例的好处是,它可以将对象的初始化和创建过程封装在一起,使代码更加简洁和易于维护。同时,使用构造函数创建对象还可以确保对象的正确创建和初始化,避免了未初始化对象所带来的安全问题。
19.虚函数与构造函数、析构函数
构造函数不能用虚函数来实现

截图来源:编程指北
在C++中,构造函数与虚函数的区别:(从功能、用法角度来谈)
1.构造函数的调用与虚函数的调用方式不同:虚函数是在运行时根据对象的实际类型来确定调用的函数,而构造函数是在编译时就确定调用的函数,因此它们的调用方式是不同的。
2.构造函数是用于初始化对象的:构造函数主要用于初始化对象,而虚函数主要用于实现多态性,它们的主要用途不同。
3.构造函数不能被继承:C++中的构造函数不能被继承,因此它们不能被子类重写,也就不能实现动态多态性。
4.构造函数没有返回值:构造函数没有返回值,因此不能使用多态特性来实现构造函数的重载和重写。
基类析构函数需要是虚函数
析构函数是进行类的清理工作,比如释放内存、关闭DB链接、关闭Socket等等。
累了,直接看吧
二、C++内存管理
1. 灵魂指针各种问题–高频
- 我们说指针存储的是变量内存的首地址,那编译器怎么知道该从首地址开始取多少个字节呢?
这就是指针类型发挥作用的时候,编译器会根据指针的所指元素的类型去判断应该取多少个字节。
不知道占据多少内存就用sizeof查一查,但是笔试题目中可能会要求自己算
作者: 编程指北
链接: https://csguide.cn/cpp/memory/understanding_of_pointers.html#_2-2-%E6%8C%87%E9%92%88%E6%9C%AC%E8%B4%A8
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

指针和引用的区别
在 C++ 中,指针和引用都是用来间接访问其他变量的机制,但它们有以下几个不同之处:
内存地址不同:指针是一个变量,它存储了另一个变量的内存地址,而引用则是目标变量的别名,它没有自己的内存地址。
用法不同:指针需要使用 * 运算符来访问目标变量的值,而引用则可以直接使用变量名来访问目标变量的值。
可空性不同:指针可以为空,即指向空地址或者未初始化的地址,而引用必须在定义时初始化,不能为空。
变量绑定不同:指针可以指向不同的变量,而引用一旦绑定到某个变量,就不能再绑定到其他变量。
作用域不同:指针可以在定义后重新赋值,甚至可以在不同的作用域中使用,而引用必须在定义时初始化,并且只能在其定义所在的作用域中使用。
需要注意的是,指针和引用都可以用于函数参数传递和返回值,但它们对函数的影响是不同的。指针作为函数参数时,可以实现函数的指针传递和修改,而引用作为函数参数时,可以实现函数的引用传递和修改。在函数返回值时,引用可以返回函数内部的变量或对象,而指针也可以返回函数内部的变量或对象,但需要注意内存泄漏的问题
2. 内存分区
C++在程序执行时,将内存大方向划分为4个区域:
代码区:存放函数体的二进制代码,由操作系统进行管理的。
全局区:存放全局变量和静态变量以及常量。
栈区:由编译器自动分配释放,存放函数的参数值、局部变量等。
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
内存四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
① 在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:代码区、全局区。
② 所有的写的代码(注释、变量、语句等)都会放到代码区中。
③栈区中的数据由编译器决定数据的生成和死亡。
④堆区中的数据由程序员决定数据的生存和死亡。
3. RAII原则
RAII(Resource Acquisition Is Initialization)是 C++ 中一种重要的编程思想,它是一种资源管理方式,用于确保程序能够正确地管理资源,避免资源泄漏和内存泄漏等问题。RAII 的核心思想是:资源的获取和释放应该是一体化的,通过对象的构造函数和析构函数来完成。
class Memory {
public:
Memory(int size) : m_ptr(new char[size]) {}
~Memory() { delete[] m_ptr; }
char* get() const { return m_ptr; }
private:
char* m_ptr;
};
int main() {
Memory mem(1024);
char* p = mem.get();
// 使用内存资源 p
return 0;
}
代码中定义了一个类 Memory,它在构造函数中分配了一块大小为 size 的内存,然后在析构函数中释放了内存。在 main 函数中,创建了一个 Memory 对象 mem,通过调用 mem.get() 获取了内存资源 p,然后使用 p 来访问内存。当 main 函数结束时,mem 对象被销毁,会自动调用析构函数来释放内存资源。

截图来源戳这里:编程指北
具体应用:智能指针
-1)智能指针是 C++ 中一种重要的 RAII 技术的应用,它是一种对象,可以自动管理指针的生命周期,避免内存泄漏和使用已经释放的内存等问题。智能指针的核心思想是:通过对象的构造函数和析构函数来管理指针,自动释放指针所指向的内存。
-2)C++ 标准库中提供了两种智能指针:std::unique_ptr 和 std::shared_ptr。
std::unique_ptr 是一种独占式的智能指针,它拥有被指向的对象,不允许其他智能指针共享该对象。当 std::unique_ptr 被销毁时,它所拥有的对象也会被销毁。
std::shared_ptr 是一种共享式的智能指针,它可以多个智能指针共享一个对象。当 std::shared_ptr 被销毁时,只有当所有共享该对象的智能指针都被销毁时,该对象才会被销毁。
std::unique_ptr<int> p(new int(42));
std::cout << *p << std::endl; // 输出 42
创建了一个 std::unique_ptr 对象 p,它指向一个动态分配的 int 对象,值为 42。当 p 对象被销毁时,它所拥有的 int 对象也会被销毁。
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2 = p1;
std::cout << *p1 << std::endl; // 输出 42
std::cout << *p2 << std::endl; // 输出 42
创建了一个 std::shared_ptr 对象 p1,它指向一个动态分配的 int 对象,值为 42。然后又创建了一个 std::shared_ptr 对象 p2,它和 p1 共享同一个对象。当 p1 和 p2 对象都被销毁时,它们所共享的 int 对象也会被销毁。
4.C++ malloc、new,free、delete 区别
malloc 和 free 是 C 语言标准库中的函数,用于动态内存分配和释放,而 new 和 delete 是 C++ 中的运算符,也用于动态内存分配和释放。它们之间的区别如下:
1.语法和使用方式不同
malloc 和 free 是函数,使用时需要包含 stdlib.h 头文件。malloc 函数用于动态分配内存,它的参数 size 表示需要分配的内存大小,返回一个指向新分配的内存的指针。free 函数用于释放由 malloc 分配的内存,它的参数 ptr 是一个指向需要释放的内存的指针。语法如下:
void* malloc(size_t size);
void free(void* ptr);
new 和 delete 是运算符,使用时不需要包含头文件,new 运算符用于动态分配内存并构造对象,它可以在不指定大小的情况下分配一个对象的内存,或者指定一个对象的初始值并分配内存。delete 运算符用于释放由 new 分配的内存并销毁对象,它的参数 ptr 是一个指向需要释放的对象的指针。delete 不会自动调用对象的析构函数,因此需要手动调用析构函数。语法如下:
new type;
new type(value);
delete ptr;
2.类型安全性和异常处理能力不同
malloc 和 free 是 C 语言标准库函数,它们不会进行类型检查,因此容易出现类型不匹配的问题。同时,它们也不支持异常处理,如果在分配内存时出现问题,只能返回一个空指针。
new 和 delete 是 C++ 中的运算符,它们会对类型进行检查,确保分配和释放的内存与对象的类型匹配。同时,它们也支持异常处理,如果在分配内存时出现问题,会抛出一个 bad_alloc 异常。
3.内存管理方式不同
malloc 和 free 是基于 C 语言的内存管理方式,它们只负责分配和释放内存,不会对对象进行构造和析构。因此,如果需要在分配内存时进行对象的构造和析构,需要额外调用构造函数和析构函数。
new 和 delete 是基于 C++ 的内存管理方式,它们除了负责分配和释放内存之外,还会自动调用对象的构造函数和析构函数,确保对象的正确创建和销毁。因此,使用 new 和 delete 更加方便和安全。
C++中异常机制的正确使用方法
try {
int *a = new int();
} catch (bad_alloc) {
...
}
作者: 编程指北
链接: https://csguide.cn/cpp/memory/malloc_and_new.html#_2-%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%A4%B1%E8%B4%A5%E6%97%B6%E8%BF%94%E5%9B%9E%E5%80%BC
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
三、C++11中的一些注意点
1. nullptr 和 NULL的区别
首先提供两个例子:
#include <iostream>
void foo(int x) {
std::cout << "foo() called with an int: " << x << std::endl;
}
void foo(char* x) {
std::cout << "foo() called with a char*: " << x << std::endl;
}
int main() {
// foo(NULL); // 编译错误:因为 NULL 会被解析为整数 0,导致二义性
foo(nullptr); // 无歧义:调用 void foo(char* x)
}
作者: 编程指北
链接: https://csguide.cn/cpp/modern_cpp/nullptr.html
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
抓住一点内容:
探究NULL和nullptr之间的区别,就是为了说明在C++中用nullptr明确定义一个空指针,因此在C++中NULL既可指代空指针又可指代整数0,那么在某些场景中就会产生二义性。例如,函数重载中,没有明确的类型定义,同名函数不知道到底应该调用重载中的哪一种才是编程人员真正想要的。
2. 类型萃取
类型萃取是 C++ 中用于获取类型信息的一种技术,可以让编译器在编译时获取类型的各种属性。它通常使用模板特化和模板偏特化来实现,可以通过模板参数推导来使用它。
template <typename T>
void foo() {
if (is_pointer<T>::value) {
std::cout << "T is a pointer type\n";
} else {
std::cout << "T is not a pointer type\n";
}
}
int main() {
foo<int>();
foo<int*>();
return 0;
}
在 foo 函数中,使用 is_pointer::value 来获取类型 T 是否是指针类型的信息。当调用 foo() 时,输出的结果是 T is not a pointer type,因为 int 不是指针类型;当调用 foo<int*>() 时,输出的结果是 T is a pointer type,因为 int* 是指针类型。
涉及字符串的使用时,什么时候用双引号什么时候用单引号?

584

被折叠的 条评论
为什么被折叠?



