C++入门-笔记(Cherno视频教程)
编译:将cpp文件变成obj文件
几个预编译指令:
● #include <XX>:实际上就是将后面文件的内容粘贴到文件中。例如:
假设定义一个x.h文件,里面内容只有一个},运行下面代码不会报错:

● #define XX YY 宏替换,就是将XX 替换成YY
● #if 表达式
.....
#endif 满足条件时才进行编译
连接:将编译后的目标文件变成可执行程序
关键字
const
- 声明常量,一旦初始化后就不能再修改其值
代码语言:cpp
代码运行次数:0
运行
AI代码解释
const int MAX_AGE = 10;
const int*age = new int; // 同 int const* age = new int; 功能一致
age = &MAX_AGE;
cout << *age << endl; //输出结果10
*age = 10 //不允许,会报错!!
// 原因:const int* age 中的 const 修饰的是 int 类型的值,
// 而不是指针本身,所以指针指向的值不能被修改,指针所指的地址可以改变
// ——————————————————————————————————————————————————————————————
const int MAX_AGE = 10;
int* const age = new int;
*age = MAX_AGE;
cout << *age << endl; //输出结果10
age = MAX_AGE; //会报错
- 放在类中函数中的参数列表后面,表示该函数不会修改类中的变量,相当于拥有“只读”功能。 如果类中没有在函数的后面加上
const,可以对m_X这个重新赋值;反之,则报错:

如果类中没有在函数的后面加上 const ,声明的静态类无法调用这个方法,反之则可以。

mutalble:声明后可以在类的const函数里改变类的成员变量值
- 对于上面的例子,如果想要在调试时查看 GetX 这个函数调用的次数,可以增加一个整型变量,且声明
mutable,否则在函数中无法修改这个变量的值。(虽然可以在 GetX 后面去掉 const 实现,但是这样的话又回导致e.GetX()这个方法报错!)——mutable使用的大部分情况

new:初始化变量,返回的是指向对象(或指向数组初始对象)的适当类型化的非零指针。在堆上分配内存
- 如果使用了
new关键字,记住必须使用delete删除变量,释放内存。
代码语言:cpp
代码运行次数:0
运行
AI代码解释
int* a = new int[23];
char *d = new char;
delete[] a; //使用new [] 就用 delete[] 删除
delete d;
explicit:用来修饰只有一个参数的类构造函数,以表明该构造函数是显式的,而非隐式的,即禁止类对象之间的隐式转换,以及禁止隐式调用拷贝构造函数。
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Entity
{
private:
int m_age;
string m_X;
public:
Entity(int age) : m_age(age), m_X("good")
{
}
Entity(string m_name)
{
m_X = m_name;
m_age = 1;
}
const string GetName() {return m_X;}
};
// 显式调用:
Entity x(1);
Entity y(string("hello"));
// 隐式调用
Entity x = 1;
Entity y = string("hello");
// 如果在上面Entity的类中构造函数前加上 explicit,则无法使用隐式调用这种
this:一个指向当前对象实例的指针
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Entity
{
public:
float x, y;
Entity(float x,float y)
{
this->x = x; //可以使用这种方式给x赋值
this->y = y;
}
};
auto:让 C++自动推导出数据的类型
● 当类型名比较长时就适合用auto,其余情况还是不要用auto,因为会导致代码可读性变差。
● 注意:auto不处理引用,所以不要漏掉&而造成一次复制
代码语言:cpp
代码运行次数:0
运行
AI代码解释
int main()
{
std::vector<std::string> strings;
strings.push_back("hello");
strings.push_back("world");
for (std::vector<std::string>::iterator it = strings.begin(); it!=strings.end(); ++it){
std::cout << *it << std::endl;
}
// 使用auto关键字简化代码
for (auto it = strings.begin(); it!=strings.end(); ++it){
std::cout << *it << std::endl;
}
}
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
class Device{};
class DeviceManager{
private:
std::unordered_map<std::string, std::vector<Device*>> m_Devices;
public:
const std::unordered_map<std::string, std::vector<Device*>>& GetDevices() const{
return m_Devices;
}
};
int main()
{
DeviceManager dm;
// const std::unordered_map<std::string, std::vector<Device*>>& devices = dm.GetDevices();
// 简化代码
// 使用using声明
// using DeviceManager = std::unordered_map<std::string, std::vector<Device*>>;
// const DeviceManager& devices = dm.GetDevices();
// 或者使用typedef
// typedef std::unordered_map<std::string, std::vector<Device*>> DeviceMap;
// const DeviceMap &devices = dm.GetDevices();
// 或者使用auto
const auto &devices = dm.GetDevices(); // 注意auto不处理引用,所以不要漏掉&而造成一次复制
// auto devices = dm.GetDevices(); # 会复制一次
}
三、数据类型
数组:有序数据的集合
数据类型 数组名常量表达式
- 数组实际上只是一个指针, 数组名本身就是指向第一个元素的指针
- 给数组某一位置复制的3种方法:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// First
int example[5];
example[2] = 10;
// Second
int* xx = example;
*(xx + 2) = 20; //将指针 xx 移动2个位置(指向 example[2]),然后解引用该位置,将其值设置为 20
// Third
*(int*)((char*)xx +8) = 30;
// 首先将指针 xx 转换为 char* 类型。这意味着指针现在以字节(char)为单位进行指针算术。
// 然后,将指针 xx 增加8个字节(sizeof(int) 通常是4个字节,因此移动8个字节相当于移动2个 int 的位置,即 example[2] 的位置)
// 最后,将这个 char* 指针转换回 int* 指针,然后解引用,并将该位置的值设置为 30。这再次修改了 example[2] 的值
- 使用 vector 创建动态数组 (C++ vector 容器浅析)
#include <vector> vector<数组中元素类型> 标识符
代码语言:cpp
代码运行次数:0
运行
AI代码解释
struct vertex
{
public:
float x, y;
};
vector<vertex> vv;
vv.push_back({1,2}); //在数组的最后添加一个数据
vv.push_back({5,6});
vv.erase(vv.begin()+1); //删除第二个元素
静态数组(std::array)
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// 使用std::arrat
std::array<int, 5> data;
data[0] = 1;
data[4] = 2;
// C风格的普通数组
int dataOld[5];
dataOld[0] = 1;
● 静态数组和普通数组的异同
○ 相同:都是在栈上分配,不像std::vector实在堆上分配的
○ 不同:std::array有边界检查(仅在debug模式下)
● 建议开始使用std::array,因为增加了一层调试,而且没有性能成本。
字符和字符串:字符使用''。字符串使用""
指针:本质就是存储内存地址的整数
- 一个变量的地址称为该变量的指针
- 指针前面的类型其实是一个说明符,指针变量的数据类型用来指定该指针变量所指向数据的类型,记住指针的本质永远是 存储内存地址的整数
数据类型标识符 *指针变量名;
代码语言:cpp
代码运行次数:0
运行
AI代码解释
int i = 100;
int* ptr = &i; //&表示获取变量的地址 但是注意:int& ref = i; 表示引用
*ptr = 5; // 这是一个解引用操作符,用于访问指针 ptr 所指向的内存地址中存储的值。

函数指针:Function pointer是将一个函数赋值给一个变量的方法
- 首先看一下简单的函数指针用法:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
void HelloWorld(){
std::cout<<"Hello World"<<std::endl;
}
int main()
{
void (*function)();
function = HelloWorld;
function();
function();
// 使用auto
auto fun= HelloWorld;
fun();
fun();
}
/*
Hello World
Hello World
Hello World
Hello World
*/
- 带参数的函数指针:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
void HelloWorld(int a)
{
std::cout << "HelloWorld:" << a << std::endl;
}
int main()
{
typedef void(*HelloWorldFunction)(int);
HelloWorldFunction function = HelloWorld;
function(3);
}
// 输出:HelloWorld:3
- 应用:将函数作为另一个函数的参数然后调用它
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <vector>
void PrintValue(int value){
std::cout << value << std::endl;
}
void ForEach(const std::vector<int>& values, void (*func)(int)){
for (int value: values){
func(value);
}
}
int main()
{
std::vector<int> values{1, 2, 3};
ForEach(values, PrintValue);
}
/*
1
2
3
*/
引用:相当于变量的一个别名,对它的操作和原来对象的操作相同
数据类型 & 表达式;
- 引用必须要进行赋值(所以上方写的是表达式);一个引用被初始化后,无法使用它 再去引用另一个变量;int a = 1; int b = 2; int &ref = a; ref = b; // 运行之后,a的值变成了2,而不是说ref被重新引用成了b
- 引用的注意事项:
- 比较下面3种不同的函数,体会函数的 值传递和引用传递
代码语言:cpp
代码运行次数:0
运行
AI代码解释
void Increment(int value)
{
value++;
}
void Increment1(int* value)
{
(*value)++;
}
void Increment2(int& value)
{
value++;
}
int main()
{
int num =1;
Increment(num);
cout << "num is: " << num << endl;
Increment1(&num);
cout << "num iss:" << num << endl;
Increment2(num);
cout << "num isss: " << num << endl;
}
// 输出:
// num is: 1
// num iss:2
// num isss: 3
枚举类型
enum 枚举类型名 {标识符列表}
- 默认从标识符的第一个元素赋值为0,如果在标识符中间某个元素指定赋值了,则后面自动累加赋值enum Example : char { A, B=65, C }; Example val=B, qwe = C; cout << val << qwe <<endl; //输出的值是 AB
- 可以指定给枚举赋值的整数类型(可以是char类型,不能是float类型)
四、类和结构体
可见性:public、private、protected
● public:对外可见,对内可见;
● private:对外不可见,对内可见;
● protected:对外不可见,对内可见,且对派生类是可见的。
- 可见性比较:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// public:类的外部也可以访问这个变量
class Father{
public:
int x,y;
public:
Father()
{
x = 10086;
}
};
class Son:public Father
{
public:
Son()
{
x = 10000;
}
};
int main()
{
Father f;
Son s;
f.x = 1;
s.x = 2;
}
使用private声明后在子类中访问会报错,外部访问更会报错;

重载运算符:运算符实际就是一个函数,所以实际上就是函数的重载
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// 实现两个实例化的类进行相加:
// 法一:定义一个相加函数
struct Vector2
{
float x, y;
Vector2(float x, float y) : x(x), y(y){}
Vector2 Add(const Vector2& other) const
{
return Vector2(x + other.x, y + other.y);
};
};
Vector2 position(4.0f, 1.0f);
Vector2 speed(5.0f, 3.0f);
Vector2 result = position.Add(speed);
// 法二:重载运算符
struct Vector2
{
float x, y;
Vector2(float x, float y) : x(x), y(y){}
Vector2 operator+(const Vector2&other)
{
return Vector2 (x + other.x, y+other.y);
};
};
Vector2 result = position + speed;
箭头操作符
- 如果实例化的类是一个指针的话,不能像其他实例化对象可以直接使用
.XX调用函数
- 总结: 箭头(->):左边必须为指针; 点号(.):左边必须为实体。
类
构造函数:特殊的方法,在创建类的实例时运行,主要用来初始化该类
- 对比下面两段代码:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Qaq
{
public:
float x, y;
void Print()
{
cout << x << ", " << y << endl;
}
};
Qaq q;
q.Print(); //输出: -1.01376e+34, 4.59163e-41
// 现在若想要进行初始化,可以声明一个init函数,但是必须在实例化后调用这个函数才能初始化;
// 如果通过构造函数的方式实现就可以自动初始化。
// 添加构造函数
class Qaq
{
public:
float x, y;
Qaq()
{,
x = 1.0f;
y = 2.0f;
}
void Print()
{
cout << x << ", " << y << endl;
}
};
int main()
{
Qaq q;
q.Print();
} //输出: 1, 2
构造函数初始化列表
在构造函数进行初始化类成员时有两种方法:
1️⃣. 常规方法:在构造函数的内部给变量赋值
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Entity
{
private:
string m_X;
public:
Entity()
{
m_X = "haha";
}
Entity(string m_name)
{
m_X = m_name;
}
const string GetName() {return m_X;}
};
int main()
{
Entity e1;
cout << e1.GetName() << endl;
Entity e2("wowo");
cout << e2.GetName() << endl;
}
/*
输出:
haha
wowo
*/
2️⃣. 在构造函数后面使用这种形式:❤(优先选择这种方式去初始化,可读性和逻辑性)
函数名 : 参数名(初始化的值)
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Entity
{
private:
int m_age;
string m_X;
public:
Entity() : m_age(23), m_X("good") // 变量的初始化顺序 和 定义类成员的顺序一致
{
}
Entity(string m_name)
{
m_X = m_name;
}
const string GetName() {return m_X;}
};
- 注意:初始化时要按照定义类成员的顺序进行初始化,否则会导致其他依赖问题!
拷贝构造函数(复制构造函数)
- 比较下面两种复制值的方式:

在类对象的复制过程中,实际上调用了默认的拷贝构造函数,也可以自定义拷贝构造函数:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
struct vector
{
float x, y;
vector(float m, float n)
: x(m), y(n) {}
vector(const vector &v)
{
x = v.x;
y = v.y;
cout << "Copy constructor called" << endl;
}
};
int main()
{
vector a = {2,3};
vector b =a;
}
// 终端输出:Copy constructor called
- 📌📌手动实现 String 类(⭐⭐⭐要素过多,反复观看)
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <string.h>
using namespace std;
class String
{
private:
char* m_Buffer; //指向字符缓冲区
unsigned int m_Size; //保存string的大小
public:
String(const char* string)
{
m_Size = strlen(string);
m_Buffer = new char[m_Size];
// 把这个指针复制到实际的缓冲区,这样缓冲区就会被我们的字符填充
// 法一:
// for (int i = 0; i < m_Size; i++)
// m_Buffer[i] = string[i];
// 法二:更简洁
memcpy(m_Buffer, string, m_Size);
}
friend ostream &operator<<(ostream &stream, const String &string);
};
// 重载输出运算符,将 `String` 对象输出到流 `std::ostream` 中。
ostream& operator<<(ostream& stream, const String& string)
{
stream << string.m_Buffer;
return stream;
}
int main()
{
String string = "Cherno";
cout << string << endl;
// cin.get();
}
上面代码存在几个问题:
1. 没有空终止字符,会导致出现随机字符

修改:
m_Buffer=newcharm_Size+1;//增加空终止符的位置 m_Bufferm_Size=0;//添加空终止符
2. 内存泄漏:分配的new char并没有delete
修改:
~String() { delete[] m_Buffer; }
3. 复制出错
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// 如果对其进行复制,会出现以下错误:
String string = "Cherno";
String second = string;
cout << string << endl;
cout << second << endl;

出错原因:运行String second = string;时 C++自动为我们做的是它将所有类成员变量,而这些成员变量组成了类(实例的内存空间),它是由一个 char*和一个 unsigned int 组成的,它将这些值复制到了一个新的内存地址里面,这个新的内存地址包含了这个 second 字符串。
现在问题来了,内存中有两个 String,因为它们直接进行了复制,这种复制被称为shallow copy(浅拷贝)。它所做的是复制这个 char,内存中的两个 String 对象有相同的 char的值,换句话说就是有相同的内存地址。这个 m_Buffer 的内存地址,对于这两个 String 对象来说是相同的,所以程序会崩溃的原因是当我们到达作用域的尽头时,这两个 String 都被销毁了,析构函数会被调用,然后执行delete[] m_Buffer两次,程序试图两次释放同一个内存块。这就是为什么程序会崩溃——因为内存已经释放了,不再是我们的了,我们无法再次释放它。

如果修改second的某个值:second[2] = 'a';(需要设置操作符[]重载),最后会出现:

- 总结:我们真正需要做的是,分配一个新的 char 数组,来存储复制的字符串。而我们现在做的是复制指针,两个字符串对象指向完全相同的内存缓冲区,它同时改变了它们,因为它们指向同一个内存块。或者当我们删除的时候它会把它们两个都删除,因为它们指向同一个内存块。 修改:自定义一个自己的拷贝构造函数: String(const String& other) : m_Size(other.m_Size) { m_Buffer = new charm_Size + 1; memcpy(m_Buffer, other.m_Buffer, m_Size + 1); }

- 最终修改后的代码(手动实现string类):
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <string.h>
using namespace std;
class String
{
private:
char* m_Buffer; //指向字符缓冲区
unsigned int m_Size; //保存string的大小
public:
String(const char* string)
{
m_Size = strlen(string);
m_Buffer = new char[m_Size + 1];
m_Buffer[m_Size] = 0;
// 把这个指针复制到实际的缓冲区,这样缓冲区就会被我们的字符填充
// 法一:
// for (int i = 0; i < m_Size; i++)
// m_Buffer[i] = string[i];
// 法二:更简洁
memcpy(m_Buffer, string, m_Size);
}
String(const String& other) : m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_Size + 1);
}
~String()
{
delete[] m_Buffer;
}
char &operator[](unsigned int index)
{
return m_Buffer[index];
}
friend ostream &operator<<(ostream &stream, const String &string);
};
// 重载输出运算符,将 `String` 对象输出到流 `std::ostream` 中。
ostream& operator<<(ostream& stream, const String& string)
{
stream << string.m_Buffer;
return stream;
}
int main()
{
String string = "Cherno";
String second = string;
second[2] = 'a';
cout << string << endl;
cout << second << endl;
cin.get();
}
cherno建议:在基础使用中用 const 引用更好。 总是要用 const 引用传递对象,因为你可以在你写的的函数的内部决定是否要复制,但是你没有理由到处复制,会拖慢你的程序。 重要的事情说三遍,当你传递字符串的时候,不管这个字符串是你自己的 String 类还是标准库里的 String(std::string),总是要通过 const 引用来传递。
C++的vector使用优化
- 观察以下代码:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
struct vertex
{
float x, y;
vertex(float x, float y) : x(x), y(y) {}
vertex(const vertex& other) : x(other.x), y(other.y)
{
cout << "Copied!." << endl;
}
};
int main()
{
vector<vertex> vv;
vv.push_back(vertex(1.0, 2.0));
vv.push_back(vertex(3.0, 4.0));
}
// 此时进行了3次复制
/*输出:
Copied!.
Copied!.
Copied!.
*/
// 如果在增加一个元素:
vv.push_back(vertex(5.0, 6.0));
// 此时进行了6次复制
/*输出:
Copied!.
Copied!.
Copied!.
Copied!.
Copied!.
Copied!.
*/
- 出现上面的原因解释:1、当我们创建 vertex 时,我们实际上是在主函数的current stack frame(当前栈帧)中构造它,所以我们是在 main 的栈上创建它。然后我们需要把这个刚创建的 vertex 从 main 函数里放到实际的 vector 中,放到 vector 分配的内存中。2、vector在这里调整了两次大小。
- 优化策略:
- 让vector一开始就留下足够的内存:
vertices.reserve(3);此时节省一般,只复制3次,如果不用reserve的话复制次数会指数倍地增长vertices.reserve(3); vertices.emplace_back(1, 2, 3); vertices.emplace_back(4, 5, 6); vertices.emplace_back(7, 8, 9); // 此时一个copies都没有了 - 使用
emplace_back,表示不是传递我们已经构建的 vertex 对象,而是只是传递了构造函数的参数列表
析构函数:与构造函数作用相反,用来释放一个对象
~类名标识符
- 直接看下面代码:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Qaq
{
public:
float x, y;
Qaq()
{
x = 1.0f;
y = 2.0f;
}
~Qaq()
{
cout << "Destroyed Qaq" << endl;
};
void Print()
{
cout << x << ", " << y << endl;
}
};
int main()
{
Qaq q;
q.Print();
}
//输出:1, 2
// Destroyed Qaq
继承
class 类名 : 继承方式 继承方式有3种:public、protected、private
虚函数:在基类中使用virtual声明函数是虚函数后,就可以允许在子类中重写这个函数
- 虚函数的使用:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
class Paper
{
public:
string GetName() { return "Paper"; }
};
class Cver : public Paper
{
private:
string m_Name;
public:
Cver(const string& name) : m_Name(name) {}
string GetName() { return m_Name; }
};
int main()
{
Paper* w = new Paper();
cout << w->GetName() << endl;
Cver* p = new Cver("Cver");
cout << p->GetName() << endl;
Paper* qq = p;
cout << qq->GetName() << endl;
}
/* 输出:
Paper
Cver
Paper
*/
//声明虚函数后
class Paper
{
public:
virtual string GetName() { return "Paper"; }
};
class Cver : public Paper
{
private:
string m_Name;
public:
Cver(const string& name) : m_Name(name) {}
string GetName() { return m_Name; }
};
int main()
{
Paper* w = new Paper();
cout << w->GetName() << endl;
Cver* p = new Cver("Cver");
cout << p->GetName() << endl;
Paper* qq = p;
cout << qq->GetName() << endl;
}
int main()
{
Paper* w = new Paper();
cout << w->GetName() << endl;
Cver* p = new Cver("Cver");
cout << p->GetName() << endl;
Paper* qq = p;
cout << qq->GetName() << endl;
}
/* 输出:
Paper
Cver
Cver
*/
纯虚函数:含有pure virual function的类称为抽象类。
virual 类型 函数名(参数列表)=0;
- 纯函数必须被实现才能创建这个类的实例
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// 比如我想要实现一个可以打印类名的功能
// 根据程序“由下而上”的思想,首先实现一个打印功能 Print()
void Print(Print_ClassName* obj)
{
cout << obj -> GetClassName() << endl;
};
// Print_ClassName是需要定义的类,其中的获取类名的函数GetClassName()
// 就可以用pure virtual function实现,然后由子类再去实现这个函数
// 定义Print_ClassName,留出GetClassName这个接口
class Print_ClassName
{
public:
virtual string GetClassName() = 0;
};
// 定义AA这个类,并且实现纯函数
class AA: public Print_ClassName
{
public:
string GetClassName() override {return "AA"; }
};
int main()
{
Print(new AA());
}
// 输出:AA
静态(static)
- 正常定义一个结构体以及声明静态变量:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
struct Entity
{
int x, y;
void Print()
{
cout << x << ", " << y << endl;
}
};
int main()
{
Entity e;
e.x = 2;
e.y = 3;
Entity e1;
e1.x = 5,
e1.y = 7;
e.Print();
e1.Print();
}
//输出结果:
// 2, 3
// 5, 7
//声明静态成员
struct Entity
{
static int x, y;
void Print()
{
cout << x << ", " << y << endl;
}
};
int Entity::x; //需要在类的外部对静态数据成员进行初始化
int Entity::y;
int main()
{
Entity e;
e.x = 2;
e.y = 3;
Entity e1;
e1.x = 5,
e1.y = 7;
e.Print();
e1.Print();
}
//输出结果:
//5, 7
//5, 7
- 注意:静态方法不能访问静态变量
五、数据结构
栈(stack)和堆(heap)
- 栈和堆是 RAM 中实际存在的两个区域: 栈通常是一个预定义大小的内存区域,通常约为 2MB左右; 堆也是一个预定义了默认值的区域,但是它可以增长,并随着应用程序的进行而改变。 这两个内存区域的实际物理位置都是在 RAM 中
- 在栈上分配内存需要用
new关键字
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// 栈上定义int array
int value = 5;
int arary[5];
// 堆上定义int array
int* value1 = new int;
*value1 = 10;
int * array1 = new int[5];
class Entity
{
public:
Entity()
{
cout << "created Entity!" << endl;
}
~Entity()
{
cout << "Destroyed Entity!" << endl;
}
};
int main()
{
Entity e; //在栈上创建的
Entity* e = new Entity; //在堆上创建的
}
- 尽量在栈上分配内存,在堆上分配的唯一原因是如果你不能在栈上分配,比如你需要让它的声明周期比你在处理的作用域更长。
栈:栈上创建的变量超出作用域就会消失
- 栈中分配内存时,一旦这个作用域结束,你在栈中分配的所有内存都会被弹出,内存被释放。这个作用域可以是任何形式,比如 main 函数,或者只是个空作用域,甚至可以使 for、while 循环等任何作用域语句。
堆:在堆中分配new后要调用delete关键字来释放内存
new关键字实际上调用了一个叫做malloc的函数的缩写,这样做通常会调用底层操作系统或平台的特定函数,这将在堆上为你分配内存。当你使用malloc请求堆内存时,它可以浏览空闲列表,找到一块符合大小要求的内存块,然后返回你一个它的指针,并记录分配的大小和它现在是否被分配的情况
六、其他小技巧
- cpp中默认情况下函数只能返回一种类型,一个特定的变量,需要返回多个相同类型的变量时可以使用vector或者数组,但是多个不同类型时,可以采用以下几种方法: 1️⃣⭐使用struct(优先使用该方法,Cherno建议) 实现一个返回字符串索引和值的 函数:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <vector>
struct str_int
{
int index;
char value;
};
std::vector<str_int> get_string_index(std::string &str){
std::vector<str_int> result;
for(int i=0; i<str.size(); i++){
str_int temp; // temp 存储了一个int类型和char类型
temp.index = i;
temp.value = str[i];
result.push_back(temp);
}
return result;
};
int main(){
std::string str = "hello";
std::vector<str_int> result = get_string_index(str);
for(auto i: result){
std::cout << i.index << " " << i.value << std::endl;
}
}
//输出:
/*
0 h
1 e
2 l
3 l
4 o
*/
2️⃣tuple和pair
● pair:使用.first 和.second 访问元素
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <vector>
std::vector<std::pair<int, char>> get_string_index(std::string &str){
std::vector<std::pair<int, char>> result;
for(int i=0; i<str.size(); i++){
std::pair<int, char> temp; // temp 存储了一个int类型和char类型
temp.first = i;
temp.second = str[i];
result.push_back(temp);
}
return result;
};
int main(){
std::string str = "hello world";
std::vector<std::pair<int, char>> result = get_string_index(str);
for(auto i: result){
std::cout << i.first << " " << i.second<< std::endl;
}
}
● tuple:使用get<i>(name) 访问元素第i个元素
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
#include <vector>
#include <tuple>
// struct str_int
// {
// int index;
// char value;
// };
std::vector<std::tuple<int, char>> get_string_index(std::string &str){
std::vector<std::tuple<int, char>> result;
for(int i=0; i<str.size(); i++){
std::tuple<int, char> temp; // temp 存储了一个int类型和char类型
std::get<0>(temp) = i;
std::get<1>(temp) = str[i];
result.push_back(temp);
}
return result;
};
int main(){
std::string str = "hello";
std::vector<std::tuple<int, char>> result = get_string_index(str);
for(auto i: result){
std::cout << std::get<0>(i) << " " << std::get<1>(i) << std::endl;
}
}
3️⃣指针和引用
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <iostream>
void returnWithReference(std::string& str, int& num)
{
str = "Hello";
num = 42;
}
int main()
{
std::string str;
int num;
returnWithReference(str, num);
std::cout << str << ", " << num << std::endl;
return 0;
}
// 输出:
/*
Hello, 42
*/
模板
- 模板允许你定义一个根据你的用途进行编译的模板,你可以让编译器基于一套规则帮你写代码。模版在调用函数时才开始根据所给的参数进行函数创建。(依赖于编译器,有些编译器只要不调用模板,其中的语法错误并不会影响编译)
template <typename 形参名, typename 形参名..> typename 可以使用class 替代
代码语言:cpp
代码运行次数:0
运行
AI代码解释
// 实现一个打印函数,由于输入类型不同,需要多次重载
#include <iostream>
#include <string>
void Print(int value)
{
std::cout << value << std::endl;
}
void Print(std::string value) // 一次函数重载
{
std::cout << value << std::endl;
}
int main()
{
Print(5);
Print("Hello");
Print(4.3f); // 这里要是再想打印一个float类型还得再重载一次
std::cin.get();
}
此时使用模版简化:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
template<typename T>
void Print(T value)
{
std::cout << value << std::endl;
}
也可以使用整数作为模版参数:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
template <int N>
class Array
{
private:
int m_Array[N];
public:
int GetSize() const { return N; }
};
int main()
{
Array<5> array;
std::cout << array.GetSize() << std::endl; // 5
std::cin.get();
}
写一个可以自动创建的类
代码语言:cpp
代码运行次数:0
运行
AI代码解释
template <typename T, int N>
class Array
{
private:
T m_Array[N];
public:
int GetSize() const { return N; }
};
int main(){
// 调用
Array<int, 5> array0;
Array<std::string, 50> array1;
}
● 总结:
○ 模板类型参数: template<typename T> 此时可以像使用任何其他类型一样使用T。
○ 非类型模板参数:允许我们在模板中使用常量值作为参数。用于在模板定义中指定一个常量值,而不是一个数据类型。非类型模板参数可以是整数、枚举、指针或引用类型。例如:template<int N>
○ 可以使用模板来做一些很好的事,比如日志系统记录每一种可能的类型,但是不要将模板写的复杂,因为后续就很难知道到底发生了什么。
宏
使用#define定义,例如 #define PI 3.14159265
- 实际上就是代码中的命令使用定义的名称替换,所以结尾的封号;也会一并替换
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#define LOG(x) std::cout << x << std::endl;
int main(){
LOG("Hello, World!")
}
// 或者
#define LOG(x) std::cout << x << std::endl
int main(){
LOG("Hello, World!");
}
- 一般使用宏不会使用上面的例子,因为你记录日志的方法可能基于你的设置会发生变化,可能希望 Debug 版本中有日志,而 Release 版本中去掉日志,而宏可以做到这一点:

- 另外,宏可以分段写,通过
\来表示换行,注意反斜杠后面不要有空格,不然就变成对空格转义了。
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#define LOG(x) std::cout << x << std::endl;
#define MAIN int main() \
{\
LOG("Hello, World!")\
}
MAIN
lambda表达式
capture list -> return type { function body }
capture list是捕获列表,用于指定 Lambda表达式可以访问的外部变量,以及是按值还是按引用的方式访问。捕获列表可以为空,表示不访问任何外部变量。parameter list是参数列表,用于表示 Lambda表达式的参数,可以为空,表示没有参数return type是返回值类型,用于指定 Lambda表达式的返回值类型,可以省略function body是函数体,用于表示 Lambda表达式的具体逻辑 =,传递所有变量,通过值传递;&传递所有变量,通过引用传递
- 例子:
std::find_if函数在values容器中查找满足 lambda 表达式的条件的第一个元素。 现在使用lambda表达式找到第一个满足条件的元素:
代码语言:cpp
代码运行次数:0
运行
AI代码解释
#include <algorithm>
auto it = std::find_if(values.begin(), values.end(), [](int value) {return value > 3; });
std::cout << *it << std::endl;
# 相当于以下的功能:
void ForEach(const std::vector<int>& values,const std::function<void(int)>& func)
{
for (int value : values)
if(value > 3) // 添加bool判断
func(value);
}
更多推荐


所有评论(0)