C++
CPP
环境配置
g++常用命令选项
选项 | 解释 |
---|---|
-ansi | 只支持ANSI标准的C语法,禁止GNU C的某些特色,如asm或typeof关键词 |
-c | 只编译并生成目标文件 |
-DMACRO | 以字符串“1”定义MACRO宏 |
-DMACRO=DEFN | 以“DEFN”定义宏 |
-E | 只运行C预编译器 |
-g | 生成调试信息 |
-IDIRECTORY | 指定额外的头文件搜索路径DIRECTORY |
-LDIRECTORY | 指定额外的函数库搜索路径DIRECTORY |
-lLIBRARY | 连接时搜索指定的函数库LIBRARY |
-m486 | 针对486进行代码优化 |
-o | FILE生成指定的输出文件,用在生成可执行文件时 |
-O0 | 不进行优化理 |
-O/-O1 | 优化生成代码 |
-O2 | 进一步优化 |
-O3 | 再进一步,包括inline函数 |
-shared | 生成共享目标文件 |
-static | 禁止使用共享链接 |
-UMACRO | 取消对MACRO宏的定义 |
-w | 不生产任何警告信息 |
-Wall | 生成所有警告信息 |
基本语法
命名空间存在的意义
为了避免有些变量名发生冲突,使用命名空间的方式来区分,也即加前缀。如C++标准库中定义了vector容器,同时自定义了一个vector类,为了区分,使用标准库中的相关容器名时都应加上std::
的前缀,同理,自定义的类中也可加个自定义的前缀。
但在不发生冲突情况下可以使用添加using namespace std
代码使接下来使用相关的类名时不需要添加前缀。
注释
1.块注释符(/……/)不可嵌套使用。
2.#if 0 …… #endif
属于条件编译,0为参数。
可使用其来实现嵌套注释,格式如下:
1 |
|
将#if 0
改为#if 1
则,code中的代码可以被执行,
tips:测试时使用#if 1
执行测试代码, 发布后用#if 0
屏蔽测试代码。
更加一般的写法是:
1 |
|
数据类型
相较于我学的C的新的内容,宽字符型wchar_t
1 |
|
wchar_t
实际空间和short int
一样。
除此之外,还有size_t
和ptrdiff_t
。
前者是一种整型类型,保存一个整数,记录以一个大小(size),全称为size type
,一种用来记录大小的数据类型,常用的sizeof(xxx)
得到的结果就是size_t
类型,它可用来做加减乘除。
后者是pointer difference type
,记录两个指针间的距离的数据类型。
两者通常是由typedef
实现。
新的类型修饰符:
修饰符 | 描述 |
---|---|
volatile | 变量可被意外改变,禁止编译器优化,与const“相对” |
mutable | 类成员可在const 对象中修改 |
注意:默认情况下,int
、short
、long
都是有符号的
数据类型 | 描述 | 大小(字节) | 范围/取值示例 |
---|---|---|---|
bool |
布尔类型,表示真或假 | 1 | true 或 false |
char |
字符类型,通常用于存储 ASCII 字符 | 1 | -128 到 127 或 0 到 255 |
signed char |
有符号字符类型 | 1 | -128 到 127 |
unsigned char |
无符号字符类型 | 1 | 0 到 255 |
wchar_t |
宽字符类型,用于存储 Unicode 字符 | 2 或 4 | 取决于平台 |
char16_t |
16 位 Unicode 字符类型(C++11 引入,无符号) | 2 | 0 到 65,535 |
char32_t |
32 位 Unicode 字符类型(C++11 引入,无符号) | 4 | 0 到 4,294,967,295 |
short |
短整型 | 2 | -32,768 到 32,767 |
unsigned short |
无符号短整型 | 2 | 0 到 65,535 |
int |
整型 | 4 | -2,147,483,648 到 2,147,483,647 |
unsigned int |
无符号整型 | 4 | 0 到 4,294,967,295 |
long |
长整型 | 4 或 8 | 取决于平台 |
unsigned long |
无符号长整型 | 4 或 8 | 取决于平台 |
long long |
长长整型(C++11 引入) | 8 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long long |
无符号长长整型(C++11 引入) | 8 | 0 到 18,446,744,073,709,551,615 |
float |
单精度浮点数 | 4 | 约 ±3.4e±38(6-7 位有效数字) |
double |
双精度浮点数 | 8 | 约 ±1.7e±308(15 位有效数字) |
long double |
扩展精度浮点数 | 8、12 或 16 | 取决于平台 |
C++新增类型
数据类型 | 描述 | 示例 |
---|---|---|
auto |
自动类型推断 | auto x = 10; |
decltype |
获取表达式的类型 | decltype(x) y = 20; |
nullptr |
空指针常量 | int* ptr = nullptr; |
std::initializer_list |
初始化列表类型 | std::initializer_list<int> list = {1, 2, 3}; |
std::tuple |
元组类型,可以存储多个不同类型的值 | std::tuple<int, float, char> t(1, 2.0, 'a'); |
派生数据类型
派生的有数组、指针、引用(变量别名,示例:int& ref = x;
)、函数、结构体、类、联合体(union,多个成员共享同一块内存)、枚举(enum,用户定义的整数常量集合)
引用
引用不是新定义了一个变量,而是给已有变量取了个别名,编译器不会为引用变量开辟内存空间,共用同一块内存空间。
==语法==: 类型& 引用变量名(对象名) = 引用实体
例:
1 |
|
特性
- 引用在定义时须初始化,是定义引用变量时,即
int& ref;
是不合法的 - 一个变量可有多个引用
- 一个引用可继续有引用,即对引用的引用实际上也是对于初始数据的引用
- 引用一旦引用一个实体,不能再引用其他实体,即:无法对初始化后的引用变量进行值的修改
- 可以对任何数据类型做引用(变量、指针……)
- 可以进行权限的保持或缩小,但不可进行权限的放大。例如:不可以对常数据类型进行引用(放大):
const int a = 2; int& b = a;
是错误的;但可以对常数据类型进行常引用(保持),或对数据类型进行常引用(缩小)。
应用
1.交换两数:
1 |
|
2.单链表的头结点修改
1 |
|
3.优化编译器因函数返回时产生的不必要临时变量
编译器在函数运行结束返回值时,无论变量存储在【栈区】、【堆区】还是【静态区】,都会先将其存放在临时变量中,回到相应调用处时再将临时变量中的内容拷贝到接受变量中。但对于存放在【静态区】的变量是不会因为函数栈帧的销毁而消亡的,因此对于此类变量,上述方式显然多此一举,对于效率有影响。
通过传引用返回可以实现权力反转,实现优化。
例:
1 |
|
对于像静态变量、全局变量、上一层栈帧、malloc的等这些出了作用域不会被编译器销毁的对象,可以使用传引用返回。
4.可以通过常引用对常量取别名,例:const int& a = 20;
5.临时变量具有常性,
1 |
|
类型别名
为现有类型定义别名通过typedef
或using
(C++11引入,例:using MyInt = int;
)
标准库类型
数据类型 | 描述 | 示例 |
---|---|---|
std::string |
字符串类型 | std::string s = "Hello"; |
std::vector |
动态数组 | std::vector<int> v = {1, 2, 3}; |
std::array |
固定大小数组(C++11 引入) | std::array<int, 3> a = {1, 2, 3}; |
std::pair |
存储两个值的容器 | std::pair<int, float> p(1, 2.0); |
std::map |
键值对容器 | std::map<int, std::string> m; |
std::set |
唯一值集合 | std::set<int> s = {1, 2, 3}; |
枚举类型
若一个变量有几种可能的值,可定义为枚举类型。即将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
枚举类型一般形式:
1 |
|
若枚举没有”整型常数”,则从第一个标识符开始。
例:
1 |
|
上述代码定义了一个颜色枚举,变量c类型为color。
默认情况下第一个名称值为0,第二个为1,以此类推。但若赋予了特殊的值,则相应的值会发生改变。
例:
1 |
|
其中blue的值为6,而red的值仍为0。
类型转换
静态转换(Static Cast)
将一种数据类型的值强制转换为另一种,常用于比较类型相似的对象间的转换,转换时不进行任何运行时类型检查,可能会引发运行时错误。
例:
1 |
|
动态转换(Dynamic Cast)
用于继承层次结构中进行向下转换的机制,将一个基类指针或引用转换为派生类指针或引用,运行时进行类型检查,若转换失败,对于指针类型返回nullptr;对于引用类型抛出std::bad_cast
异常。
语法:
dynamic_cast<目标类型>(表达式)
(目标类型是指针或引用类型,表达式为需转换的基类指针或引用)
常量转换(Const Cast)
将const类型的对象转换为非const类型的对象,不能改变对象的类型,只能用于转换掉const属性。
例:
1 |
|
重新解释转换(Reinterpret Cast)
将一个数据类型的值重新解释为另一个,常用于不同数据类型间的转换,不进行任何类型检查,可能导致未定义行为。
typedef和#define间的区别
执行时间不同
typedef在编译阶段有效,因此会进行类型检查。
#define发生在预处理阶段,即编译之前,只进行简单而机械的字符串替换,不进行任何检查。
功能差异
前者用来定义类型的别名;后者不只可为类型取别名,还可定义常量、变量和编译开关等。
作用域不同
后者无作用域限制,前者有自己的作用域。
对指针的操作
两者修饰指针形式时,作用不同。
1 |
|
变量类型
注意
1.不带初始化的定义:若带有静态存储持续时间的变量会被隐式初始化为NULL(所有字节的值都是0),其他所有变量的初始值是未定义的。
2.使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的)。可以使用extern
在任何地方声明一个变量。
例:
1 |
|
3.定义包含了声明,但声明不包含定义,如
1 |
|
C++中的左右值
左值
指向内存位置的表达式称为左值表达式,其可出现在赋值号的左边或右边。
右值
指存储在内存中某些地址的数值。是不可对其进行赋值的表达式,即右值表达式可出现在赋值号右边,但不能出现在左边。
变量是左值,所以可以在左边。数值型的字面值为右值,不能被赋值,不能出现在左边。
C++变量作用域
共6种,全局作用域、局部作用域、语句作用域、类作用域、命名空间作用域和文件作用域。
全局变量、局部变量、静态全局变量和静态局部变量
作用域
全局变量有全局作用域,只需在一个源文件中定义,就可作用所有源文件,其他不含全局变量定义的源文件需用extern
关键字再声明。
静态局部变量有局部作用域,只初始化一次,从第一次初始化后直到程序运行结束一直存在,与全局变量不同的是其只对定义自己的函数体可见。
局部变量只有局部作用域,自动对象,运行期间不一直存在,只在函数执行期间存在,一次调用执行结束后,变量撤销,占用内存收回。
静态全局变量有全局作用域,不同于全局变量,若程序包含多个文件,则静态全局变量作用于定义它的文件里,不能作用到其他文件里,即被static
关键字修饰过的变量有文件作用域,这样即使两个不同的源文件都定义了相同名字的静态全局变量,也是不同的变量。
分配内存空间
全局变量、静态局部变量和静态全局变量都在静态存储区分配,而局部变量在栈中分配。
将局部变量修饰为静态是改变了其存储方式即改变了生存期,不改变作用范围;将全局变量修饰为静态是改变了作用域,限制其使用范围,不改变存储位置。
Tips
1.若全局变量仅在单个文件中访问,可将其修饰为静态全局变量。
2.若全局变量仅由单个函数访问,可将其修改为改函数的静态局部变量。
3.函数中必须使用static
变量:如某函数的返回值为指针类型,则必须是static
的局部变量的地址作为返回值;
4.MyClass::class_var
中的::
是作用域解析运算符,用于访问类的静态成员,而std::cout
中的::
用于指定cout
是std
命名空间中的对象。
5.对于全局变量的引用以及重新赋值,直接使用全局变量名会出现:变量不明确的问题。在变量名前加上::
即可像正常变量一样使用。
常量
整型常量带后缀U或L,U表示无符号整数,L表示长整数,可大写可小写。
浮点常量由整数部分、小数点、小数部分和指数部分组成,可使用小数形式或指数形式表示。
小数形式需包含整数部分、小数部分,或同时包含两者。3.14159
指数形式,须包含小数点、指数,或同时有,带符号指数由e或E引入。314159E-5L
字符常量括在单引号中,若常量以L(仅当大写时)开头 ,其表示一个宽字符常量(如:L’x’),则此时其必须存储在wchar_t
类型的变量中,否则,其为窄字符常量(如’x’),其可存储在char
类型的简单变量中。
转义序列 | 含义 |
---|---|
\? | ?字符 |
\a | 警报铃声(声音不是从声卡上放出来,来自主板上的蜂鸣器) |
\b | 退格键 |
\ooo | 一到三位八进制数 |
\xhh | 一个或多个数字的十六进制数 |
可以使用\
做分割一个很长字符串常量,使之分行,例如:
1 |
|
一般使用**#define预处理器和const**来定义常量。
两者区别:
类型和安全检查不同
宏定义为字符替换,没有数据类型的区别,且此替换无类型安全检查,可能产生边际效应等错误。
const常量有类型区别,需在编译阶段进行类型检查。
编译器处理不同
宏定义是“编译时”,在预处理阶段展开,不能对宏定义进行调试。
const常量是“运行时”,类似一个只读行数据。
存储方式不同
宏定义直接替换,不分配内存,存储于程序的代码段中。
const常量需要进行内存分配,存储于程序的数据段中。
定义域不同
宏定义对全局有效,const只对对应定义处的函数有效。
定于后能否取消
宏定义可通过#undef
使之前的宏定义失效
const常量定义后将在定义域内永久有效。
是否能作为函数参数
宏定义不能,const常量可以。
注意:const常量定义时必须赋初值,除非其是用extern
修饰的外部变量。
const char* , char const* , char* const的区别
将一个声明从右向左读
将*读成 pointer to
1 |
|
类型限定符
提供变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字.
限定符 | 含义 |
---|---|
const | const 定义常量,表示该变量的值不能被修改。 |
volatile | 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。其往往用于多线程的修饰。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
mutable | mutable 用于修饰类的成员变量。被 mutable 修饰的成员变量可以被修改,即使它们所在的对象是 const 的。 |
static | 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。 |
register | 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。 |
explicit | 可阻止不应允许的经过转换构造函数进行的隐式转换的发生,即声明为explicit的构造函数不能在隐式转换中使用 |
1 |
|
存储类
定义变量/函数的范围(可见性)和生命周期,放置在它们修饰的类型之前。
存储类 | 含义 |
---|---|
auto | 默认存储类说明符,可省略。修饰的变量有自动存储期,即生命周期仅限于定义它们的块,常在栈上分配。 |
register | 用于建议编译器将变量存储在CPU寄存器中以提高访问速度。 |
static | 定义有静态存储期的变量或函数,生命周期贯穿整个程序的运行期。函数内,static变量的值在函数调用间不变;文件内或全局作用域中,有内部链接,只能在定义它们的文件中访问。 |
extern | 声明有外部链接的变量或函数,可在多个文件间共享。默认情况下,全局变量和函数有extern存储类。在一个文件中使用extern声明另一个文件中定义的全局变量或函数,可以实现跨文件共享。 |
mutable | 用于修饰类中的成员变量,允许在const成员函数中修改这些变量的值。通常用于缓存或计数器等需要在const上下文中修改的数据。 |
thread_local | 定义有线程存储期的变量,每个线程都有自己的独立副本。线程局部变量的生命周期与线程的生命周期相同。 |
auto存储类
常用于两种情况:声明变量时根据初始化表达式自动推断变量的类型和声明函数时函数返回值的占位符
static存储类
指示编译器在程序的生命周期内保持局部变量的存在,不需要在每次进入和离开作用域时进行创建和销毁。使用static变量可在函数调用间保持局部变量的值。
作用于全局变量时,会使其作用域限制在声明它的文件中。
作用在类数据成员上时,会使仅有一个该成员的副本被类的所有对象共享。
extern存储类
其提供一个全局变量的引用,使其对所有程序文件都可见。
extern是用来在另一个文件中声明一个全局变量或函数,常用于当有两个或多个文件共享相同的全局变量或函数时。
例:
1 |
|
通过$ g++ main.cpp support.cpp -o write
命令编译得到write可执行文件。再通过$ ./write
或者$ .\write
运行得到相应的输出Count is 5
.
mutable存储类
使被修饰的类的成员变量能在const成员函数中被修改。
常用于需要不改变对象外部状态的情况下进行状态管理的场景,如缓存、延迟计算等。
缓存:在const函数中计算并缓存结果,不影响对象的外部状态。
状态跟踪:如日志计数器,跟踪调用次数等信息,避免对类逻辑进行侵入式修改。
实例:
1 |
|
thread_local存储类
用于多线程环境中管理线程特有的变量。
使用其修饰的变量在每个线程中都有独立的实例,因此每个线程对该变量的操作不会影响其他线程。
具有如下性质:
- 独立性:每个线程有独有的变量副本,不同线程间读写操作互不影响。
- thread_local变量在线程结束时自动销毁
- thread_local变量可进行静态初始化或动态初始化,支持在声明时初始化。
适合用于需要存储线程状态、缓存或避免数竞争的场景,如线程池、请求上下文等。
函数
Lambda函数与表达式
Lambda表达式将函数看作对象,Lambda表达式可以像对象一样使用,如可以将其赋给变量和作为参数传递,还可像函数一样对其求值。
具体形式:
[capture](parameters) mutable ->return_type{body}
(parameters)
在不需要参数传递时可以包括括号一起省略。
mutable
,lambda函数一般是一个const函数,mutable可以取消其常量性,若使用mutable
,则即使参数为空,参数列表也不能省略。
->return_type
是用于追踪返回类型形式声明函数的返回类型,无返回值时可全部省略。
{body}
除了可以使用参数外,还可使用所有捕获的变量。
例如:
[](int x, int y){return x < y ;}
若无返回值可以表示为:
[capture](parameters){body}
例如:
[]{ ++global_x; }
一个更复杂的例子:
[](int x, int y) -> int { int z = x + y; return z + x; }
该例中z被创建来存储中间结果,其值不会保留到下一次该表达式再次被调用时。
Lambda表达式中可访问当前作用域的变量,这是Lambda表达式的闭包行为,变量传递的传值和传引用的区别,通过[]
指定:
1 |
|
对于[=]
或[&]
的形式,Lambda可直接使用this
指针,但对[]
的形式,要使用this
,必须显式传入:[this](){ this->someFunc();}();
使用实例
1 |
|