C++学习笔记
2025-04-22 16:34:17 0 举报
AI智能生成
这些珍贵的C++学习笔记包含了多种核心内容,例如C++的基本语法结构、控制流(如条件语句和循环结构)、函数的声明和定义、以及面向对象编程(类、对象、继承、多态性)。此外,笔记还详细阐述了模板、异常处理、标准模板库(STL)等高级主题。这些文件涵盖从基础知识到高级概念的广泛范围,适合初学者和有一定经验的开发者来巩固和扩展C++编程知识。每个主题都辅以示例代码和实践练习,使读者能直观理解并应用所学概念。【AI生成】
作者其他创作
大纲/内容
指针
指针是一个变量,其值是另一个变量的地址,即:内存位置的直接地址
声明:
type *var-name;
空指针
NULL, 是一个定义在标准库的值为零的常量
不可以访问
野指针
释放内存后未能及时将指针置空,仍然指向原来位置,使指针指向内容不可预知
解决拌饭
初始化时使用NULL,释放后使用NULL,使用智能指针
算数运算
支持++, --, +, -
指针和数组
数组本质是不可修改的指针
指针数组和数组指针
指针数组
一组指针的数组
数组指针
指向数组的指针
区别
例子
传递指针给函数
函数返回指针
子主题
指针大小
32位
4字节
64位
8字节
无类型指针void*
void*
可以指向任意类型的指针,但不包含类型信息,也因此无法进行指针运算
灵活性
通常用于接口或者库,例如内存管理函数malloc和free,以及其他接受各种数据类型的函数
缺乏安全性
举例
void* ptr = malloc(sizeof(int)); // 分配内存并返回 void*
int* intPtr = static_cast<int*>(ptr); // 转换为 int*
*intPtr = 42; // 使用指向的整数
int* intPtr = static_cast<int*>(ptr); // 转换为 int*
*intPtr = 42; // 使用指向的整数
智能指针
引用
与指针区别
不存在空引用,引用必须连接到一块合法内存
一旦引用被初始化为一个对象,就不能再指向其他对象,指针随时可以
引用必须在创建时被初始化,指针可以在任何时间被初始化
本质:是指针的语法糖
左值 & 右值
关系到生命周期和移动语义
左值引用 Lvalue Ref
左值引用是指向一个可以被取地址并且有持久生命周期的对象的引用。左值是指在
表达式中可以取地址并且具有名称的对象。左值通常表示存储在某个内存位置的对象。
表达式中可以取地址并且具有名称的对象。左值通常表示存储在某个内存位置的对象。
也被叫做locator Value
即,有用存储空间的值
特点
可以赋值后被修改
可以绑定到具名对象上
举例
int a = 10;
int& refA = a; //refA 是a 的左值引用
int& refA = a; //refA 是a 的左值引用
右值引用 Rvalue Ref
右值引用是指向临时对象或不再需要的对象的引用。右值是指没有名称的对象,
通常是临时对象,它们不能被取地址。
通常是临时对象,它们不能被取地址。
举例
int&& temp = 10; // temp是一个右值引用,10是右值
例子
使用情景
用于函数参数
笔记:三种传参方式
用于函数返回值
谈谈人文:为什么既有指针又有引用
内存操作
new
分配空间,并创建对象实例, 返回对象指针
与malloc()区别:malloc只分配空间,不创建对象,即:不会调用构造函数,返回对象指针
delete
delete ptr 与delete[] ptr
delete ptr是删除该指针占用的内存空间
deletel[] ptr删除指针指向所有数组元素的内存空间
如果数组使用 delelte ptr,将会只释放头个元素的内存,而剩余元素得不到释放,造成内存泄漏
时间与日期
四个类型
clock_t
time_t
size_t
tm
重要函数
time_t time(time_t *time);
该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 -1。
char *ctime(const time_t *time);
该返回一个表示当地时间的字符串指针,字符串形式 day month year hours:minutes:seconds year\n\0。
struct tm *localtime(const time_t *time);
该函数返回一个指向表示本地时间的 tm 结构的指针。
clock_t clock(void);
该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 -1。
char * asctime ( const struct tm * time );
该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\0。
struct tm *gmtime(const time_t *time);
该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示。
time_t mktime(struct tm *time);
该函数返回日历时间,相当于 time 所指向结构中存储的时间。
double difftime ( time_t time2, time_t time1 );
该函数返回 time1 和 time2 之间相差的秒数。
size_t strftime();
该函数可用于格式化日期和时间为指定的格式。
示例
UE C++ 注意点
申明了FORCEINLINE关键字的内联函数,不能和void一起使用。不然就会报错。
虚幻引擎中,基本数据类型包括,uint8、uint16、uint32、uint64、int8、 int16、int32、int64、float。但是在蓝图中只支持uint8和int32。其他类型在c++中是支持的,但是如果你一但你将这些变量暴露给蓝图,编辑就会报错。
类中UPROPERTY() 不支持常量。
类中不支持static const 类型的初始化。
UFUNCTION()修饰的反射函数的形式参数要求全部是UCLASS、USTRUCT或者UENUM。
在类的成员变量初始化列表中,初始化的顺序应该按照类中成员从上到下的顺序初始化,否则将收到编译器的警告。
CreateDefaultSubobject只能写在类的无参构造函数中,否则崩溃。
SetupAttachment只能写在构造函数中,否则崩溃。
最好每个变量都声明UPROPERTY(),否则变量将不会进行自动内存管理,变量的生命周期将不可控。
内联函数
代码
目的
提高运行效率。内联函数在程序编译时,会尝试将内联函数内的代码直接替换原来调用函数的代码,
当然,编译器会根据具体的情况决定是否进行这样的替换。
函数调用时,通常涉及压栈、跳转和返回等操作,当函数被内联时,由于直接替换了代码,因此可以
消除这一步开销以提高性能。
当然,编译器会根据具体的情况决定是否进行这样的替换。
函数调用时,通常涉及压栈、跳转和返回等操作,当函数被内联时,由于直接替换了代码,因此可以
消除这一步开销以提高性能。
注意点
虚函数,递归函数不会被编译器内联
一般都是少量的几行代码,代码量过大时,就要考虑是否有必要声明为内联
不要再内联中使用循环和开关语句
内联函数有可能导致代码膨胀
static
const
基本作用:将变量声明为常量,约束其无法被修改;
形参前面加const作用
将形参作为一个常量,保护其不在函数中被修改
与&结合使用:既可以使对象在函数调用时不产生临时对象而提高性能,又可以保证其不被修改
传入实参既可以是变量,也可以是常量
结合指针使用
常量指针
指向常量的指针
指针常量
指针本身是常量
结合引用使用
道理同上,不过引用本身就是常量,所以没有“引用常量”这样的说法
通常用在函数形参中,作用如上
常量概念
顶层常量
C++中几乎所有赋值都会忽略等号两边的顶层常量,
底层常量
通常情况下底层常量只可以赋值给另一个常量
函数末尾加const
标记该函数为只读函数,该方法不能修改所属对象的类成员;也不能调用非const函数
const函数中,只有被mutable标记的成员变量可以被修改
对象声明为const
该对象只有const成员函数可以调用,非const函数将无法调用
函数返回值加const
表示返回值不能被修改
mutable
深入理解
左值,右值,移动语义
after c++11
左值lvalue
具有地址,存储在内存中
可以出现在等号的左侧
可以取址&
常见左值
变量
对象
数组元素
右值rvalue
通常没有地址,存储在寄存器或临时内存中
不能出现在=等左侧
不能取址&
常见右值
表达式计算结果
非静态类成员函数本身(函数指针指向的函数地址)
lambda表达式
枚举
数组
this指针
xvalue
右值引用使用场景
触发移动构造,避免深拷贝带来的系统消耗。但是旧的引用将指向空
实现完美转发
完美转发
常量表达式
编程技巧
卫语句(Guard)
消除if嵌套,前置检测提前返回,简洁易读
代码写完交给gpt优化
有趣的思考
到底是char* p还是char *p
C++书中说法,那个习惯用哪个
为什么既有指针又有引用
引用是指针的语法糖,为了简化指针的写法
函数进化
函数 -> 函数指针 -> 函数模板 -> 仿函数/函数对象 -> lamda表达式
闭包
左右值
友元friend
友元函数
不是类成员函数,但是可以访问类的私有和保护成员
不受访问控制符限制
友元类
友元的关系是单向的
友元成员函数
需要向前声明(forward declaration)解决循环依赖问题
特点
不可继承
单向,且不可传递
破坏了类的封装性,谨慎使用
提前声明
类的提前声明
使用场景:
当你只需要使用类的指针或引用时,可以提前声明类,而不需要包含完整的类定义。
提前声明只是声明了存在这么一个类型,但是类型中具体的内容是不知道的
限制
不能访问类的成员
不能用作基类
不能定义成员函数
函数的提前声明
提前声明函数,可以在定义之前调用它
限制
不能提前声明函数模板,必须在使用前定义
模板的提前声明
模板的提前声明通常用于解决循环依赖问
枚举的提前声明
C++11中引入,提前声明时必须指定底层类型
各版本
C++11
C++14
C++17
C++20
其他
从语言到二进制文件过程
预编译
(1) 将所有的#define删除,并且展开所有的宏定义
(2) 处理所有的条件预编译指令,如#if、#ifdef
(3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
(4) 过滤所有的注释
(5) 添加行号和文件名标识。
编译
(1) 词法分析:将源代码的字符序列分割成一系列的记号。
(2) 语法分析:对记号进行语法分析,产生语法树。
(3) 语义分析:判断表达式是否有意义。
(4) 代码优化:
(5) 目标代码生成:生成汇编代码。
(6) 目标代码优化:
汇编
这个过程主要是将汇编代码转变成机器可以执行的指令。
链接
将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
链接分为静态链接和动态链接。
静态链接,是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。
而动态链接,是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当你删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。
nullptr可以调用成员函数吗?
能。因为在编译时对象就绑定了函数地址,和指针空不空没关系。
示例
因为在编译时对象就绑定了函数地址,和指针空不空没关系。pAn->breathe();编译的时候,函数的地址就和指针pAn绑定了;调用breath(*this), this就等于pAn。由于函数中没有需要解引用this的地方,所以函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。
静态变量,全局变量,局部变量区别
内联函数和宏函数区别
声明和定义
变量类型
注意:不同版本c++标准有区别
整数类型
short / short int
短整型
通常2字节
范围
int
整数
通常4字节
范围
long
长整型
通常4字节
范围
long long
更长的整数
通常8字节
范围
浮点类型
float
通常4字节
double
通常8字节
long double
字符类型
char
wchar_t宽字符
typedef short int wchar_t;
与短整型一致
char16_t
表示Unicode字符
2字节
char32_t
4字节
布尔类型
true 或者false
枚举类型
指针类型
type*
数组
结构体
struct
类
class
共用体
union
结构体
与类区别
class 中默认的成员访问权限是 private 的,而 struct 中则是 public 的
从 class 继承默认是 private 继承,而从 struct 继承默认是 public 继承
class 可以定义模板,而 struct 不可以
限制成员位数
结构体的成员,在定义时候可以限制其位数多少,
例如:struct demo{int demoint:1;};
这样成员demoint就只占一位(B)
例如:struct demo{int demoint:1;};
这样成员demoint就只占一位(B)
C的结构不能有函数,C++可以有
类
构造函数&析构函数
构造函数
初始化列表
顺序
可以重载,可以有多个
举例
析构函数
析构函数只能有一个
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数
定义形式:
调用时机
作为函数参数,以值传递方式传入函数体
作为函数返回值,以值传递方式从函数返回
用于初始化同类型的对象
必须定义的情况
初始化
拷贝初始化
直接初始化
对于其他类型没有区别,仅对于类有区别
模板
定义
语法
template <typename/class T>
typename用于模板函数 ?
class用于模板类
特殊:可变参数模板Variadic Templates
template<typename... T>
这种语法允许函数接受任意数量和类型的参数
在函数体内部,可以使用ArgsType&&... Args来声明一个参数的引用(包括右值引用),
并且在调用其他函数时,可以使用std::forward<ArgsType>(Args)...来将
这些参数完美转发(perfect forwarding)给其他函数。
这确保了参数的值类别(lvalue或rvalue)得以保持。
并且在调用其他函数时,可以使用std::forward<ArgsType>(Args)...来将
这些参数完美转发(perfect forwarding)给其他函数。
这确保了参数的值类别(lvalue或rvalue)得以保持。
编译器编译时会实例化所有出现过的参数类型版本
获取参数个数
sizeof...(args)
通常使用递归方式展开
与C#泛型区别
编译时vs运行时
c++模板是编译时机制,即,在编译时将代码替换成具体的类型,(模板膨胀)
C#是运行时机制,依靠.Net CLR机制
类型约束
C++20以前没有明确的类型约束,实例化时才会进行类型检查。可能导致难以调试的编译错误
C#有明确的类型约束
特化
C++模板支持特化,可以针对特性类型编写特化代码
C#泛型不支持特化,可以通过方法重载或者显式接口实现
动态类型支持
C++时静态的类型,编译后即是固定的类型
C#支持与反射结合使用,可以在运行时操作泛型类型
性能和代码体积
C++性能更高,但会产生二进制膨胀
C#的CLR共享引用类型的泛型代码,减少了内存占用;对值类型会生成特性代码版本,但没有像C++严重的代码膨胀问题
预处理器
#include
#define
#if
#else
#line
条件编译
#ifdef 如果定义了某个符号常量
则编译此处的代码
#else
否则编译此处代码
#endif 结束条件编译
则编译此处的代码
#else
否则编译此处代码
#endif 结束条件编译
#if 0
不再编译此处代码
#endif
不再编译此处代码
#endif
C++标准的预定义宏
可变参数
...包扩展运算符
c风格可变参数函数
使用 va_list, va_start, va_arg, 和 va_end 来访问参数。
易出错,建议使用C++的可变参数模板
可变参数模板
重载
运算符重载
本质是函数重载
可重载运算符
不可重载运算符
函数重载
类重载、覆盖、重定义之间的区别:
重载指的是函数具有的不同的参数列表,而函数名相同的函数。重载要求参数列表必须不同,比如参数的类型不同、参数的个数不同、参数的顺序不同。如果仅仅是函数的返回值不同是没办法重载的,因为重载要求参数列表必须不同。(发生在同一个类里)
覆盖是存在类中,子类重写从基类继承过来的函数。被重写的函数不能是static的。必须是virtual的。但是函数名、返回值、参数列表都必须和基类相同(发生在基类和子类)
重定义也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。(发生在基类和子类)
重载指的是函数具有的不同的参数列表,而函数名相同的函数。重载要求参数列表必须不同,比如参数的类型不同、参数的个数不同、参数的顺序不同。如果仅仅是函数的返回值不同是没办法重载的,因为重载要求参数列表必须不同。(发生在同一个类里)
覆盖是存在类中,子类重写从基类继承过来的函数。被重写的函数不能是static的。必须是virtual的。但是函数名、返回值、参数列表都必须和基类相同(发生在基类和子类)
重定义也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。(发生在基类和子类)
函数进化
仿函数
将类封装成函数样子,使其类似于函数调用
重载 () 操作符
特点
因为本质是对象,所以可以拥有成员变量从而持有状态
为了更好的可读性
匿名函数
Lambda(c++11)
语法
[capture list] (param list) -> return type {function body}
capture list
[]:默认不捕获任何变量;
[=]:默认以值捕获所有变量;
[&]:默认以引用捕获所有变量;
[x]:仅以值捕获x,其它变量不捕获;
[&x]:仅以引用捕获x,其它变量不捕获;
[=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
[&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
[this]:通过引用捕获当前对象(其实是复制指针);
[*this]:通过传值方式捕获当前对象;
[=]:默认以值捕获所有变量;
[&]:默认以引用捕获所有变量;
[x]:仅以值捕获x,其它变量不捕获;
[&x]:仅以引用捕获x,其它变量不捕获;
[=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
[&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
[this]:通过引用捕获当前对象(其实是复制指针);
[*this]:通过传值方式捕获当前对象;
param list
c++14增加了auto关键字用以支持泛型参数
return type
可省略
当有多个返回位置时,不可省略
function body
当定义一个lambda 表达式后,
编译器自动生成一个重载了运算符()的匿名类,
称之为闭包类型 closure type
编译器自动生成一个重载了运算符()的匿名类,
称之为闭包类型 closure type
函数指针
关键字
explicit
防止类实例化时的隐式转换
如果一个类拥有一个只有一个参数的构造函数,那么这个类可以隐式构建
例如 class MyClass{
int Num;
public:
MyClass(int val){
Num = val;
}
该类可以使用MyClass m = 11; 调用
在单参数的构造函数前加入explicit 关键字可以禁用这种隐式转换
例如 class MyClass{
int Num;
public:
MyClass(int val){
Num = val;
}
该类可以使用MyClass m = 11; 调用
在单参数的构造函数前加入explicit 关键字可以禁用这种隐式转换
防止类型运算符隐式转换
防止模板类型中隐式转换可能导致推到错误
内存结构
静态存储区
静态变量
堆
使用new分配,使用delete删除
栈
局部变量
0 条评论
下一页