数据类型
C++ 基本数据类型
基本整数类型
类型名 | 类型大小(字节) | 理论上最小字节数 | 字面量前后缀 |
---|---|---|---|
singed char | 1 | 1 | |
unsinged char | 1 | 1 | |
singed short int | 2 | 2 | |
unsinged short int | 2 | 2 | |
singed int | 4 | 4 | |
unsinged int | 4 | 4 | U 后缀 |
singed long int | 4 或 8 | 4 | L 后缀 |
unsinged long int | 4 或 8 | 4 | UL 后缀 |
singed long long int | 8 | 8 | LL 后缀 |
unsinged long long int | 8 | 8 | ULL 后缀 |
wchar_t | 2 | 2 (宽字符) | L 前缀 |
char16_t | 2 | 2 (UTF-16字符) | u 前缀 |
char32_t | 4 | 4 (UTF-32字符) | U 前缀 |
std::byte 也可以用于表示单字节类型的二进制数字。
基本浮点数类型
类型名 | 说明 | 精度(十进制数位的) | 字面量后缀 |
---|---|---|---|
float | 单精度浮点数 | 7 | F |
double | 双精度浮点数 | 15 或 16 | |
long double | 扩展双精度浮点数 | 18 或 19 | L |
初始化方式
赋值式初始化
1 | int var1 = 100; |
构造式初始化
1 | int var2(200); |
列表初始化
列表初始化是使用花括号来进行初始化:
1 | int var1 = { 0 }; |
也可以用花括号对 vector 等STL容器对象进行初始化:
1 | std::vector<int> intv; |
函数返回数值列表,例如:
1 | std::vector<std::string> getList() |
类内元素初始化:
列表式初始化也可以用于类内的初始化,(类内初始化不能使用圆括号,因为会与函数定义混淆),例如:
1 | class T1 |
auto 推导变量类型
auto 关键字可以让编译帮你自动推导出变量的类型:
1 | auto var1 = 30; // var1 为 int 类型 |
decltype 推导类型
decltype 可以返回操作数的类型:
1 | decltype(2) var2 = 3; // 这里根据2的类型推导出 var2 的类型为 int |
const 限定符
const 限定符用于指明变量的值是固定不变的:
1 | const int count = 1000; |
const 是修饰与它最接近的右边的指出的对象,遇到类型则跳过继续向右,上述 const 是修饰 count 变量,表示 count 变量是不可改变的。所以更推荐写成:
1 | int const count = 1000; |
constexpr 限定符
将变量定义为constexpr,表示该变量是一个常量表达式。定义为constexpr变量,编译器需要在编译的时候就确定变量的值。
1 | constexpr int size1() |
如果使用一个函数的返回值,赋值给 constexpr 变量,则这个函数一定要声明为 constexpr 类型。
二、constexpr 函数
constexpr 函数可以用来赋值给 constexpr 变量。
constexpr 函数返回值和形参都必须是字面值类型,而且只有一条 return 语句,
1 | constexpr int var4 = 59; |
constexpr 函数和 inline 函数一样,可以定义在 .h 头文件中,被多个源文件包含使用。
constexpr 函数会很神奇地在编译时就运行得到结果。
title2-1
title2-1-1
safdasfdsfds
title2-2
dsfgsdfdsf
sdfdsfdsfdsf
三、 类型别名
方式1:
typedef long long verylong;
方式2:
using verylong = long long;
一、constexpr 变量
一、基于范围的 for 循环
基于范围的循环,可以使用 for 语句来实现:
for (类型名 变量名 : 序列)
{
// 处理语句
}
例如:
1 int var_list1[] = { 1, 100, -1, 300, 400 };
2 for (auto x : var_list1)
3 {
4 printf(“%d “, x);
5 }
6
7 std::string str1(“Hello World”);
8 for (auto c : str1)
9 {
10 printf(“%c “, c);
11 }
12
13 std::vectorstd::string str_list = {“str1”, “str2”, “str3”, “str4”};
14 for (auto s : str_list)
15 {
16 printf(“%s “, s.c_str());
17 }
一、默认构造函数、析构函数、复制控制成员
使用 default 关键字可以声明一个默认的构造函数、析构函数、复制控制成员函数,不带任何参数。
1 class CName
2 {
3 public:
4 CName() = default;
5 CName(const CName &) = default;
6 CName & operator = (const CName &);
7 ~CName() = default;
8 };
9
10 CName & CName::operator = (const CName &) = default;
二、类内初始化
可以使用等号赋值、或者花括号初始化的方式进行类内初始化。构造函数中的初始化会覆盖类内初始化。
1 class T1
2 {
3 public:
4 int member1 { 100 };
5 int member2 = 200;
6 std::string member3 = std::string(“Hello World”);
7 };
三、委托构造函数
一个构造函数的初始化,通过使用其他同类中的构造函数来完成自己的初始化过程,这个构造函数叫委托构造函数:
1 class ClassA
2 {
3 private:
4 int a,b,c;
5 public:
6 ClassA(int _a, int _b, int _c) : a(_a), b(_b), c(_c)
7 {
8 }
9
10 ClassA(std::string name) : ClassA(1, 2, 3)
11 {
12 }
13 };
四、constexpr 构造函数
字面值常量的类构造函数可以是 constexpr 的,也必须至少提供过一个 constexpr 构造函数
1 class Point
2 {
3 public:
4 constexpr Point(int _x, int _y) : x(_x), y(_y)
5 {
6 }
7 constexpr Point() : x(0), y(0)
8 {
9 }
10
11 int x, y;
12 };
13
14 constexpr Point pt = {100, 200};
五、删除的函数
成员函数中,后面加上 =delete 可以把函数定位删除的函数。就是虽然声明了它,但是不能以任何方式来使用它。例如:
1 class CannotCopy
2 {
3 CannotCopy() = default;
4 ~CannotCopy() = default;
5 // 防止拷贝
6 CannotCopy(const CannotCopy&) = delete;
7 // 防止通过赋值进行拷贝
8 CannotCopy& operator=(const CannotCopy&) = delete;
9 };
六、右值引用
概念1,左值:一个左值表达式,通常是表示一个对象的身份,左值是一个持久的状态。
概念1,右值:一个右值表达式,通常是表示一个对象的值,右值是一个字面量或者临时的对象的状态。
通过 && 操作符来定义获得一个右值的引用,用来绑定临时的对象:
1 int var1 = 100;
2 int & r1 = var1; // 左值引用
3 int && r2 = var1; // 编译错误,右值引用不能绑定左值
4 int & r3 = var1 + 200; // 编译错误,左值引用不能绑定右值
5 int && r4 = var1 + 200; // 右值引用
6 const int & r5 = var1 + 300; // const左值引用可以绑定右值
利用 std::move 函数可以将一个左值转换为对应的右值引用的类型。
七、移动构造函数和移动赋值运算符
移动构造函数和移动赋值运算符,其参数应该是该类型的右值引用。用于完成资源的移动,并确保移动后,源对象不再引用已经移动的资源,确保源对象对资源是不再具有所有权的。
1 class MyClass
2 {
3 public:
4 MyClass() = default;
5 // 移动构造函数
6 MyClass(MyClass && src) noexcept;
7 // 移动赋值运算符
8 MyClass & operator = (MyClass && src) noexcept;
9 };
一般移动构造函数和移动赋值运算符都标记为 noexcept,不抛出异常的移动构造函数和移动赋值运算符都必须声明为 noexcept。
移动之后的源对象,必须能正常析构,不能出错。
编译器默认不会自动合成一个移动构造函数。如果只定义了拷贝构造函数,没有定义移动构造函数,即使使用了std::move,还是会用拷贝构造函数来构造对象。
八、派生类中的虚函数
override 关键字,可以避免派生类中忘记重写虚函数的错误。
在派生类的虚函数中,标明 override 关键字,可以避免基类成员不是 virtual、成员函数返回类型、参数、异常规格、常量属性、引用限定符等不相匹配的情形。出现不匹配的情况,及其容易产生BUG。如果不匹配,编译器就会报错,帮助快速定位到错误。
1 class Base
2 {
3 public:
4 virtual int func1() const;
5 };
6 class Derived : public Base
7 {
8 public:
9 virtual int func1() const override;
10 };
九、 final 关键字
final 关键字修饰成员函数的时候,将不允许子类对此成员函数进行覆盖。
1 class Base
2 {
3 public:
4 virtual int func1() const final; // 已经定义为final
5 };
6 class Derived : public Base
7 {
8 public:
9 virtual int func1() const; // 这里会导致编译错误
10 };
final 关键字修饰类的时候,整个类都不允许再被继承:
1 class Base final
2 {
3 ////
4 };
5 class ClassB final : public ClassA
6 {
7 };
十、enum
1. 带限定作用域的enum:使用 enum class 或者 enum struct 来声明枚举:
1 enum class color
2 {
3 red,
4 green,
5 blue
6 };
7 color myColor = color::blue;
2. 指定枚举的类型
默认情况下,枚举采用整数的类型进行内部表示。可以指定 enum 具体采用的整数类型,例如:
1 enum class NewEnumName: unsigned long long
2 {
3 ValueA, ValueB, ValueC
4 };
3. 枚举类型的前置声明
枚举类型可以前置声明,到源码的后面才进行指定成员的值,但是不带作用域的 enum 前置声明必须指出枚举的类型大小。
1 enum MyType1 : unsigned long long;
2 enum class MyType2; // 默认内置大小为 int
shared_ptr 为共享指针,以模板的形式存在。需要包含的头文件为 #include
一、使用原始指针定义与解引用:
1 std::shared_ptr
2 std::cout << *pdd << std::endl;
二、未初始化的 shared_ptr 默认值为 nullptr
1 std::shared_ptr
2 if (pdata == nullptr)
3 {
4 printf(“pdata is nullptr\n”);
5 }
三、最佳初始化方法:使用 std :: make_shared 函数
1 std::shared_ptr
2 std::shared_ptrstd::string p2 = std::make_sharedstd::string(100, ‘c’);
3 auto p3 = std::make_shared
4 std::shared_ptr
四、引用计数与自动销毁
1 auto p = std::make_shared
2 auto q(p); // p 和 q 指向相同内存,这时已经有p,q两个引用者,引用计数为2
3 auto r = q; // p, q, r 指向相同内存,引用计数为3
当一个共享指针的引用计数变为0的时候,系统会自动调用指向的对象的析构函数,进行对象的自动销毁。
赋值操作的引用计数变化:
1 auto p = std::make_shared
2 auto q = std::make_shared
3 auto p = q; // p引用计数先减为0,释放p指向的内存
4 // 赋值之后q的引用计数增加变成2,p引用计数也变成2
五、获得引用计数值,判断是否唯一引用
1 auto p = std::make_shared
2 int count = p.use_count(); // use_count()方法获得引用计数值
3 bool uq = p.unique(); // unique()方法获得当前是否唯一引用
六、使用 get() 方法获取原始指针
使用 get() 方法可以得到原始指针,有利于某些需要原始指针操作的地方。
使用 get() 不会影响引用计数,也不会影响自动析构与销毁。
不能手动使用 delete 来释放 get() 出来的原始指针。
七、自定义删除器
默认情况下,智能指针的删除器是使用 delete 操作。但是如果分配的是数组,则不适合,这时需要自定义删除器。
1 void mydeleter(int * pdata)
2 {
3 delete[] pdata;
4 }
5 std::shared_ptr
6
八、不能使用 ++, – – 和 [] 运算符
九、 智能指针的交换
1. 类成员函数: swap(p, q);
2. 成员函数: p.swap(q);
十、释放智能指针的引用
1. pdata = nullptr;
赋予 nullptr 值,会触发释放智能指针的引用
2. pdata.reset(); 或者 pdata.reset(nullptr);
十一、智能指针的重置
使用 reset() 方法对智能指针进行复位。
pdata.reset(new int(1000));
将 pdata 的引用计数减去 1,如果引用计数为0则删除对象,然后再指向新指针,引用计数变为 1 。
weak_ptr 是弱引用智能指针,不单独使用,配合 sheard_ptr 一起使用。
使用已有的 shared_ptr 来初始化 weak_ptr :
1 std::shared_ptr
2 std::weak_ptr
weak_ptr 对指向的内存没有所有权,其引用不会改变 shared_ptr 的引用计数。
weak_ptr 不能使用 * 和 -> 进行解引用,只用使用 lock() 方法尝试转换为 shared_ptr 来使用:
1 std::shared_ptr
2 std::weak_ptr
3 if (std::shared_ptr
4 {
5 printf(“%d\n”, *np);
6 }
如果 weak_ptr 已经过期,则 lock() 方法返回 nullptr 的 shared_ptr。
weak_ptr 的 expired() 方法用来判断引用的 shared_ptr 是否过期,如果过期则返回 true。
unique_ptr 为独占拥有的智能指针,与 shared_ptr 的最大区别就在于独占拥有,独享所有权。
使用 std::make_unique 创建 unique_ptr 对象,只有到 C++ 14 标准才支持:
std::unique
unique_ptr 对象不可复制,只能移动。无法通过复制构造函数或赋值运算符创建 unique_ptr 对象的副本。
可以使用 std::move 将 unique_ptr 转换为右值引用的方式,将所有权赋予给另一个 unique_ptr:
1 std::unique<MyClass> pObj1 = std::make_unique<MyClass>(int param1);
2 std::unique<MyClass> pObj2 = std::move(pObj1);
3 // 这时 pObj1 的值为 nullptr
4 // pObj2 则关联引用到第一行所创建的对象
释放原始指针:
使用 release() 方法可以将 unique_ptr 释放出原始指针,但是释放出来的原始指针必须自己手动调用 delete 或者 delete[]
进行释放释放内存。
1 MyClass * p = pObj2.release();
2 // to-do other
3 delete p;
如果不想自己手动使用 delete/delete[] 来释放内存,应该使用 get() 方法来获取原始指针。
C++中的 lambda 表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数。
Lambda表达式表示方式如下:
[捕获列表] (参数列表) -> 返回类型 { 函数体 }
1. 捕获列表列出 lambda 可直接使用父函数的局部变量的列表,可以为空,必须保留 [ ]
2. 参数列表允许不写,不存在参数的话,不需要保留 ( )
3. 返回类型不写的话,默认为 void
4. 函数体必须有
最简洁的 lambda 可以写成: auto f = [] {};
声明 lambda 的时候,编译器会自动生成一个跟 lambda 对应的匿名的类类型。传递 lambda 的时候,编译器会定义一个跟lambda 对应的匿名的类类型的对象。
一、值捕获
变量的捕获跟参数传递一样,可以采用值捕获,也可以采用引用捕获。例如:
1 auto f = [ var1 ] (int param1) { return var1 + param1; }
二、引用捕获。 例如:
1 auto f = [ &var1 ] (int param1) { var1 = param1; }
lambda 捕获的都是局部变量,引用捕获需要确保 lambda 体语句执行的时候,引用捕获的对象还正常存在。
三、隐式捕获
让编译器根据 lambda 中的代码使用情况自动推断捕获对象,这种方式叫隐式捕获,例如:
1 auto f = [ = ] (int param1) { } // 隐式值捕获
2 auto f = [ & ] (int param1) { } // 隐式引用捕获
四、混合捕获
1 auto f = [ =, &c ] (int param1) { } // c采用引用捕获,其他采用隐式值捕获
2 auto f = [ &, c ] (int param1) { } // c采用值捕获,其他采用隐式引用捕获
五、改变值捕获的值
通过值捕获的 lambda,也可以使用 mutable 关键字来改变,例如:
1 auto f = [ c ] () mutable { return ++c; } // c采用值捕获,参数列表后加mutable允许改变其值
六、利用 std::bind 生成可调用对象
1. 包含头文件 #include
2. std::bind 调用形式:
auto callable_object = std::bind(callable, arg-list);
调用 callable_object 时,callable_object 会调用 callable,并传入 arg-list 中的参数列表。