nullptr
NULL只是一个宏定义,而nullptr是一个C++关键字。
nullptr关键字用于标识空指针,是std::nullptr_t类型的(constexpr)变量。它可以转换成任何指针类型和bool布尔类型(主要是为了兼容普通指针可以作为条件判断语句的写法),但是不能被转换为整数。
因为NULL被定义为0。则在某些使用场景则会出错。建议大家都使用nullptr。
auto
auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型。使用auto定义变量时必须初始化。
auto的自动类型推断发生在编译期,即使用auto并不会造成程序运行时效率的降低。
真正编程的时候不建议使用auto,而是直接写出变量的类型更加清晰易懂。当在需要简化的时候可以使用auto来代替冗长的类型定义。
1、不能将auto作为形参编写。
2、不能用于非静态类的成员初始化。
3、不能定义数组。
4、不能推导出函数参数。
1、可以遍历容器。
1、可以用于泛型编程。
std::vector<int> vec;
for (auto i = vec.begin(); i != vec.end(); i++)
{
//..
}
/*********/
/*
auto通常可配合decltype来使用,decltype和auto都可以用来推断类型。
decltype可推断源类型,例如const,而auto则只可推出类型。
*/
double decltypeTest(){ return 3.14; }
int x = 100;
decltype(x) y;
int sz1 = sizeof(z); // 4
decltype(decltypeTest()) z;
int sz2 = sizeof(z); // 8
/*********/
explicit
有时构造函数自动类型转换函数,但这种自动特性并非总是合乎要求的,有时会导致意外的类型转换。C++新增了关键字explicit,用于关闭这种自动特性。即被explicit关键字修饰的类构造函数,不能进行自动地隐式类型转换,只能显式地进行类型转换。
range-based for
快速循环
// 整数循环输出
for (int i : {1, 2, 3, 4, 5}) { std::cout << i; }
// 字符串循环输出
for (auto i : {"hi", "test", "abc"}) { std::cout << i ; }
// 容器循环输出,并将容器内容均改为"OK!"
std::vector<std::string> ss = {"hello","world","C++"};
for (auto &str : ss)
{
std::cout << str;
str = "OK!";
}
=default 和 =delete
声明自定义的类型之后,编译器会默认生成一些成员函数,这些函数被称为默认函数。
显式缺省函数(=default)
声明时在函数末尾加上”= default”来显式地指示编译器去生成该函数的默认版本
显式删除函数(=delete)
在函数的定义或者声明后面加上"= delete"就能实现限制一些默认函数的生成。
原始字面量
当指定某路径时候若使用"\"来作为路径则解析将会失败。
那么可以通过增加 R"(str)" 来翻译str为普通字符,该功能一般用于字符串内由转义部分的场景。
// 未使用原始字面量
std::string path1 = "D:\ABC\DEF\1.txt";
char cath1[] = "D:\ABC\DEF\1.txt";
std::cout << "path1:" << path1 << std::endl; // path1:D:ABCDEF.txt
std::cout << "cath1:" << cath1<< std::endl; // path1:D:ABCDEF.txt
// 使用原始字面量
std::string path2 = R"(D:\ABC\DEF\1.txt)";
char cath2[] = R"(D:\ABC\DEF\1.txt)";
std::cout << "path2:" << path2 << std::endl; // path2:D:\ABC\DEF\1.txt
std::cout << "cath2:" << cath2<< std::endl; // path2:D:\ABC\DEF\1.txt
同样在书写多行代码时,也可使用原始字面量:
std::string str = R"(
abs,
aac,
ok!
)";
注意: R"()" 左括号前和右括号后可以书写备注信息,但注意两边的备注信息需要一致否则会报错。备注信息在编译器解析时会自动忽略掉。
constexpr
限定在了编译期常量计算出来,而const只保证了运行时不直接被修改(但const仍然可能是个动态变量)。在非某些场景下其实const与constexpr是相同的。
与 const 不同,constexpr 也可应用于函数和类构造函数。 constexpr 指示值或返回值是常数,并且如果可能,将在编译时计算值或返回值。
const 变量的初始化可以延迟到运行时,而 constexpr 变量必须在编译时进行初始化。所有 constexpr 变量均为常量,因此必须使用常量表达式初始化。
注意:constexpr不可用于创建class、struct等自定义类型:
constexpr struct TEST{}; // 错误
struct TEST{};
constexpr TEST Test; // 正确
在某些场景中,必须明确使用 constexpr:
constexpr int sqr1(int arg) {return arg*arg;}
const int sqr2(int arg) {return arg*arg;}
int sqr3(int arg) {return arg*arg;}
std::array<int, sqr1(10)> mylist1; // 正确
std::array<int, sqr2(10)> mylist2; // 错误
std::array<int, sqr3(10)> mylist3; // 错误
返回类型后置
final
override
确保派生类中声明的重写函数与基类虚函数有相同的签名。同时也明确表明将会重写基类的虚函数。增加了正确性、可读性。
lambda表达式
labmda表达式是现代编程语言的特点。声明式的编程风格、避免代码膨胀与功能分散、实现功能闭包增加程序灵活性。
labmda表达式定义了一个匿名函数,可以捕获一定范围内的变量。
(注意:若需调用该匿名函数则在匿名函数表达式后面加入"()",若需传参则写入该括号内)
// lambda表达式简单语法归纳
[ capture ] ( params ) opt->ret { body };
capture: 捕获列表。捕获一定范围内的变量。
具体使用:
[] 不捕获任何变量。
[&] 捕获外部作用域中所有变量,并作为引用在函数体内使用。(引用捕获)
[=] 捕获外部作用域中所有变量,并作为副本在函数体内使用。拷贝的副本在匿名函数内是只读的。(按值捕获)
[=,&x] 捕获外部作用域中所有变量,并按引用方式捕获外部变量x。
[x] 按值捕获外部变量x。
[&x] 按引用捕获外部变量x。
[this] 捕获当前类中的this。
params: 参数列表。和普通函数的参数列表一样,如没有参数则参数列表可省略。
opt: 函数选项。如不需要可省略。可定义传递参数的形式、可定义抛出异常。
mutable 在匿名函数内可以修改按值传递进来的拷贝,但不会影响函数外部的参数值。
exception 指定函数抛出的异常。如抛出整形的异常可用throw()。
ret: 返回类型。返回值是通过返回值后置语法来定义的。
body: 函数体。函数的实现,如没有则函数体可以为空。
--------------------------------------------------------------------
// 空的匿名函数。
[](){};
// 引用方式捕获外部变量给函数体内使用,并传入int参数。
int x = 10;
[&](int abc){};
// x作为值传递方式给匿名函数内部使用,若没有mutable声明则不允许函数内部修改x。值得说明,即便增加了mutable声明,x仅在匿名函数内部修改生效,而函数外部的x不会发生任何改变。
int x = 10;
[x]()mutable{++x;};
// 基本同上。匿名函数指定了返回值为int,并对该匿名函数进行了调用。
int x = 10;
int y = [x]()mutable ->int{return ++x; }(); // y = 11
// 基本同上。匿名函数指定了传入double参数并传入了double参数。
int x = 9;
double a = 3.14;
int y = [x](double A)mutable ->int {return ++x * A; }(a); // y = 31
--------------------------------------------------------------------
/*使用函数指针对匿名函数进行调用*/
// 只有当捕获列表为空时,可以使用函数指针对匿名函数进行调用。
double (*lp)(int);
lp = [](int x)->double {return x * 3.14; };
double y = lp(10); // y = 31.4
// 使用包装器对匿名函数的值传递捕获方式进行调用。
#include <functional>
int A = 10;
std::function<int(double)> lp = [=](double B)->int {return A * B; };
int y = lp(3.14); // y = 31
// 使用绑定器对匿名函数的值传递捕获方式进行调用。
int A = 10;
std::function<int(double)> lp = std::bind([=](double B)->int {return A * B; }, std::placeholders::_1);
int y = lp(3.14); // y = 31
右值引用
右值引用可延长右值的使用周期,直到右值引用释放。右值引用标记为"&&"
左值:可以取地址操作。
右值:不可取地址操作。
// 右值引用(仅可使用"右值"来初始化)
int &&x = 8;
// 左值引用(可使用"左值"或"右值引用"来初始化)
int A = 10;
int &B = A;
int &C = x;
共享智能指针
智能指针是存储指向动态分配(堆)对象指针的类。用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄漏。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数-1,减为0时,删除所指向的堆内存。
C++11提供了三种智能指针,使用这些智能指针时需要引用头文件;
std::shared_ptr : 共享的智能指针
std::unique_ptr : 独占的智能指针
std::weak_ptr :弱引用智能指针,不共享指针,不能操作资源,是用来监视std::shared_ptr的。
std::shared_ptr 共享智能指针
多个智能指针可以同时管理同一块有效的内存。共享内存指针是一个模板类。初始化完毕后就指向要管理的那块堆内存,若要查看当前有多少个智能指针同时管理这块内存可以使用共享智能指针提供的成员函数use_count。
有三种初始化方式:
-------------- 1、构造函数 - std::shared_ptr<T> 智能指针名字(创建堆内存); --------------
// 使用共享智能指针,不管理任何内存
std::shared_ptr<int> ptr1;
if (ptr1.use_count() != 0)
{std::cout << "ptr1:" << ptr1.use_count() << " " << ptr1 << " " << *ptr1 << std::endl;}
// 使用共享智能指针管理一块字符数组的堆内存
std::shared_ptr<int> ptr2(nullptr);
if (ptr2.use_count() != 0)
{std::cout << "ptr2:" << ptr2.use_count() << " " << ptr2 << " " << *ptr2 << std::endl;}
// 使用共享智能指针管理一块int型堆内存
std::shared_ptr<int> ptr3(new int(9527));
std::cout << "ptr3:" << ptr3.use_count() << " " << ptr3 << " " << *ptr3 << std::endl;
// 使用共享智能指针管理一块字符数组的堆内存
std::shared_ptr<char> ptr4(new char[10]);
std::cout << "ptr4:" << ptr4.use_count() << " " << ptr4 << " " << *ptr4 << std::endl;
-------------- 2、通过拷贝和移动构造函数初始化 --------------
std::shared_ptr<int> ptr1(new int(9527)); // ptr1.use_count() == 1
std::shared_ptr<int> ptr2(ptr1); // ptr1.use_count() == 2
std::shared_ptr<int> ptr3(ptr2); // ptr1.use_count() == 3
std::shared_ptr<int> ptr4(ptr3); // ptr1.use_count() == 4
std::shared_ptr<int> ptr5(std::move(ptr1)); // ptr1.use_count() == 4,此时ptr1不再管理该堆内存,而ptr5接手管理该堆内存。
// 注意:
int *ptr = new int(9527);
std::shared_ptr<int> ptr5(ptr);
std::shared_ptr<int> ptr6(ptr); // 不能再用ptr用来做初始了。编译不会错,但在运行时异常。
td::cout << "ptr5:" << ptr5.use_count() << " " << ptr5 << " " << *ptr5 << std::endl;
-------------- 3、std::make_shared辅助函数 --------------
std::shared_ptr<int> ptr1 = std::make_shared<int>(9527); // ptr1.use_count() == 1
std::shared_ptr<int> ptr2 = std::make_shared<int>(); // ptr2.use_count() == 1
-------------- 4、reset方法 --------------
/*reset方法是对赋值对象先做解除指向,然后再进行分配。*/
std::shared_ptr<int> ptr1 = std::make_shared<int>(9527);
使用方法1: ptr1.reset(); // 释放ptr1
使用方法1: ptr1.reset(new int(100)); // 释放ptr1,然后重新为ptr1分配
// 备注:reset()释放后,智能指针会自动调用删除器。若是类对象则调用析构函数。
使用共享智能指针:
class T
{
public:
T() {}
void setABC(int t) { abc = t; }
int getABC() { return abc; }
private:
int abc = 100;
};
int main()
{
// 创建共享智能指针
std::shared_ptr<T> ptr(new T);
// 使用原始指针操作
ptr.use_count();
T *tPtr = ptr.get();
tPtr->getABC();
tPtr->setABC(200);
// 直接使用共享智能指针操作
ptr->getABC();
ptr->setABC(300);
// 最终,该共享智能指针会自动析构。
return 0;
}
共享智能指针指定删除器
当共享智能指针引用计数为0时,这块内存就会被析构掉。当然我们也可以自己来指定删除,即删除器。删除器本质上是一个回调函数,也可以使用匿名函数。
// 回调函数
void tDelete(int *ptr) { if (ptr) { delete ptr; }ptr = nullptr; }
// 创建共享智能指针
std::shared_ptr<int> ptr(new int(100), tDelete);
// 使用匿名函数
std::shared_ptr<int> ptr2(new int(100), [](int *ptr2) {if (ptr2) { delete ptr2; }ptr2 = nullptr; });
注意,自定义删除器通常是为了删除掉数组类型的智能指针。因为C++11默认删除器仅会对地址首删除,那么数组中其他内存不能被析构。
// 数组举例。C++11必须自定义删除器对所有数组内存析构。
std::shared_ptr<int> ptr(new int[10], [](int *ptr) {if (ptr) { delete []ptr; }ptr = nullptr; });
使用std::shared_ptr的注意事项:
1、不能使用一个原始地址初始化多个共享智能指针。
2、函数不能返回管理了this的共享智能指针对象。
3、共享智能指针不能循环引用。
std::unique_ptr 独占智能指针
与共享智能指针类似,但独占智能指针是只允许一个智能指针管理内存。可对独占智能指针做move操作,将管理权转让给其他智能指针。
独占智能指针使用方法与共享智能指针基本相同。
--------------------- 初始化方式 ---------------------
// 1、构造函数
std::unique_ptr<int> ptr(new int(100)); // 正确。
std::unique_ptr<int> ptr2 = ptr; // 错误,不可赋值初始化。
std::unique_ptr<int[]> ptr3(new int[5]); // 正确。并且默认删除器也可对所有数组内存析构。
// 2、函数返回
std::unique_ptr<int> GetUnique() { return std::unique_ptr<int>(new int(9527)); }
std::unique_ptr<int> ptr = GetUnique();
// 3、move
std::unique_ptr<int> ptr(new int(100));
std::unique_ptr<int> ptr2 = std::move(ptr);
独占智能指针的自定义删除器与共享智能指针删除器不一样,独占的需要加入删除器的类型。
// 独占智能指针自定义删除器时需指定删除器类型。
std::unique_ptr<int, void(*)(int *)> ptr(new int(100), [](int *ptr) {if (ptr) { delete ptr; }ptr = nullptr; });
// 同上,但此时匿名函数使用外部值捕获方式。
std::unique_ptr<int, std::function<void(int *)>> ptr(new int(100), [=](int *ptr) {if (ptr) { delete ptr; }ptr = nullptr; });
// 此时,将独占智能指针指向数组类型。那么默认删除器也可以对数组内存全部析构。但
std::weak_ptr 弱引用智能指针
std::weak_ptr可以看作是std::shared_ptr的助手。作为一个旁观者监视std::shared_ptr中管理资源是否存在。
--------------- 初始化 ---------------
1、默认构造函数
constexpr std::weak_ptr() noexcept;
// 例: std::weak_ptr<int> ptr2;
2、拷贝拷贝构造函数
std::weak_ptr(const std::weak_ptr &x)noexcept;
template<class U> std::weak_ptr(const std::weak_ptr<U> &x)noexcept;
/*
例:
std::weak_ptr<int> ptr;
std::weak_ptr<int> ptr2(ptr);
std::weak_ptr<int> ptr3 = ptr2;
*/
3、通过std::shared_ptr对象构造
template<class U> std::weak_ptr(const std::shared_ptr<U> &x)noexcept;
/*
例:
std::shared_ptr<int> ptr(new int);
std::weak_ptr<int> ptr2(ptr);
std::weak_ptr<int> ptr3 = ptr;
*/
--------------- 相关函数 ---------------
// expired() 判断观测的资源是否被释放。
bool expired()const noexcept;
/*
例:
std::shared_ptr<int> ptr1(new int);
std::weak_ptr<int> ptr2(ptr1);
bool b1 = ptr2.expired(); // false
ptr2.reset();
bool b2 = ptr2.expired(); // true
*/
// lock() 获取管理所监测资源的std::shared_ptr对象。
std::shared_ptr<T> lock()const noexcept;
/*
std::shared_ptr<int> ptr1(new int(100)),ptr2;
std::weak_ptr<int> ptr3(ptr1);
ptr2 = ptr3.lock();
// 那么ptr3.use_count() == 2。注意,std::weak_ptr.lock会使引用计数+1。
*/
std::call_once、td :: once_flag
C++11引入的新特性,如需使用,只需要#include 即可,简单来说std:call_once的作用,确保函数或代码片段在多线程环境下,只需要执行一次,常用的场景如Init()操作或一些系统参数的获取等。
#include <iostream>
#include <mutex>
/* 简单使用1 - lambda */
/*
输出:
9527
*/
int main()
{
std::once_flag flag;
std::call_once(flag, [=] {std::cout << "9527"; });
return 0;
}
/* 简单使用2 - 调用显式函数 */
/*
输出:
onceFun:9527
onceFun:9527
onceFun:9527
-------
onceFun:9527
*/
void onceFun(int index) { std::cout << "onceFun:" << index; }
int main()
{
for (short index = 0; index < 3; ++index)
{
std::once_flag flag;
std::call_once(flag, onceFun, 9527);
}
std::cout << "-------\n";
for (short index = 0; index < 3; ++index)
{
static std::once_flag flag;
std::call_once(flag, onceFun, 9527);
}
return 0;
}
/* 简单使用3 - 调用类的静态函数 */
/*
输出:
onceFun:9527
*/
class onceClass
{
public:
static void onceFun(onceClass oc) { qDebug() << "onceFun:" << oc.value; }
private:
int value = 9527;
};
int main()
{
onceClass oc;
std::once_flag flag;
for (short index = 0; index < 3; ++index)
{
std::call_once(flag, &onceClass::onceFun, oc);
}
return 0;
}
关注
笔者 - jxd
微信公众号搜索 “码农总动员” 或 微信扫描下方二维码,了解更多你不知道的XX,O(∩_∩)O