数据类型

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
2
3
int var1 = { 0 };
int var2 { 0 };
int var3 {}; // 初始化列表值为 0 可以省略, 与 {0} 效果相同

也可以用花括号对 vector 等STL容器对象进行初始化:

1
2
3
4
5
6
7
8
std::vector<int> intv;
intv = {1, 2, 3, 4, 5, 6, 7, 8};

// 用列表初始化形式来初始化 new vector 对象:
std::vector<int> * pv = new vector<int> {100, 200, 300, 400};

// 用列表初始化形式来初始化 new 数组:
int * pia = new int[10] {0,1,2,3,4,5,6,7,8,9};

函数返回数值列表,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::vector<std::string> getList()
{
return {"ok1", "ok2", "ok3"};
}

std::pair<std::string, int> func1()
{
return {"key", 10086}; // 以列表初始化形式返回
}

std::pair<string, int> func2()
{
return std::pair<std::string, int>(); // 以构造函数返回
}

类内元素初始化:

列表式初始化也可以用于类内的初始化,(类内初始化不能使用圆括号,因为会与函数定义混淆),例如:

1
2
3
4
5
6
7
class T1
{
public:
int member1 { 100 };
int member2 = 200;
std::string member3 = std::string("Hello World");
};

auto 推导变量类型

auto 关键字可以让编译帮你自动推导出变量的类型:

1
2
3
4
5
auto var1 = 30;                 // var1 为 int 类型
auto var2 = 1000LL; // var1 为 long long 类型
auto pi1 = new auto(100); // pi1 为 int * 类型
auto pi2 = new auto{200}; // pi2 为 int * 类型 (列表式初始化 auto 只能推导单个值)
auto pi3 = new auto{300, 400}; // 编译不通过, 利用 auto 推导只能单个列表初始化

decltype 推导类型

decltype 可以返回操作数的类型:

1
2
decltype(2) var2 = 3;          // 这里根据2的类型推导出 var2 的类型为 int
decltype(fun()) sum = xx; // 这里推导出 sum 的类型为 函数fun()返回值 的类型

const 限定符

const 限定符用于指明变量的值是固定不变的:

1
const int count = 1000;

const 是修饰与它最接近的右边的指出的对象,遇到类型则跳过继续向右,上述 const 是修饰 count 变量,表示 count 变量是不可改变的。所以更推荐写成:

1
int const count = 1000;

constexpr 限定符

将变量定义为constexpr,表示该变量是一个常量表达式。定义为constexpr变量,编译器需要在编译的时候就确定变量的值。

1
2
3
4
5
6
7
8
constexpr int size1()
{
return 3;
}

constexpr int var1 = 100;
constexpr int var2 = var1 + 200;
constexpr int var3 = size1();

如果使用一个函数的返回值,赋值给 constexpr 变量,则这个函数一定要声明为 constexpr 类型。

二、constexpr 函数
constexpr 函数可以用来赋值给 constexpr 变量。
constexpr 函数返回值和形参都必须是字面值类型,而且只有一条 return 语句,

1
2
3
4
5
6
7
constexpr int var4 = 59;
constexpr int size2(int arg)
{
return arg * var4;
}

constexpr int var5 = size2(50);

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 pdd { new double {999.99} };
2 std::cout << *pdd << std::endl;

二、未初始化的 shared_ptr 默认值为 nullptr
1 std::shared_ptr pdata;
2 if (pdata == nullptr)
3 {
4 printf(“pdata is nullptr\n”);
5 }

三、最佳初始化方法:使用 std :: make_shared 函数
1 std::shared_ptr p1 = std::make_shared(89);
2 std::shared_ptrstd::string p2 = std::make_sharedstd::string(100, ‘c’);
3 auto p3 = std::make_shared(100); // 自动推导p3的类型
4 std::shared_ptr p4 { std::make_shared(102) }; // 花括号初始化

四、引用计数与自动销毁
1 auto p = std::make_shared(1000); // p指向的内存只有p一个引用,引用计数为1
2 auto q(p); // p 和 q 指向相同内存,这时已经有p,q两个引用者,引用计数为2
3 auto r = q; // p, q, r 指向相同内存,引用计数为3
当一个共享指针的引用计数变为0的时候,系统会自动调用指向的对象的析构函数,进行对象的自动销毁。
赋值操作的引用计数变化:
1 auto p = std::make_shared(1000); // p引用计数为1
2 auto q = std::make_shared(2000); // q引用计数为1
3 auto p = q; // p引用计数先减为0,释放p指向的内存
4 // 赋值之后q的引用计数增加变成2,p引用计数也变成2

五、获得引用计数值,判断是否唯一引用
1 auto p = std::make_shared(1000);
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 p6(new int[1000], mydeleter);
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 sp (new int);
2 std::weak_ptr wp(sp);

weak_ptr 对指向的内存没有所有权,其引用不会改变 shared_ptr 的引用计数。

weak_ptr 不能使用 * 和 -> 进行解引用,只用使用 lock() 方法尝试转换为 shared_ptr 来使用:
1 std::shared_ptr sp1(new int(10));
2 std::weak_ptr wp(sp1);
3 if (std::shared_ptr np = wp.lock())
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 myObj = std::make_unique(int param1);

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 中的参数列表。