参考的资料《高速上手C++11/14》
弃用不是废弃,而是避免使用,为了兼容性,可能会永久保留的内容。
// foo.h
#ifdef __cpulsplus
extern "C"{
#endif
int add(int x, int y);
#ifdef __cplusplus
}
#endif
// foo.c
int add(int x, int y){
return x+y;
}
// main.cpp
#include "foo.h"
int main(){
add(1, 2);
return 0;
}
->>>>>
gcc -c foo.c // 编译出foo.o
g++ main.cpp foo.o -o main // 将C++代码和.o文件链接起来
nullptr 取代 NULL 因为NULL有时会被视作0。
constexpr 修饰函数,函数显式的返回一个常数。
for(std::vector<int>::const_iterator iter = vec.cbegin(); iter != vec.cend(); ++iter)
---->>>
for(auto iter = vec.cbegin(); iter != vec.cend(); ++iter)
auto不可以做为函数传参->可以使用函数模板进行重载
decltype 关键字是为了解决auto关键字只能对变量进行类型推导的缺陷而出现的。
auto x = 1;
auto y = 2;
decltype(x+y) z;
// 传统C++
template<typename R, typename T, typename U>
R add(T x, U y){
return x+y;
}// 程序员需要明确指出返回类型;
// 尾返回类型C++11
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
// C++14 nice~~~
template<typename T, typename U>
auto add(T x, U y){
return x+y;
}
基于范围的for循环
for(auto &i : arr) // std::vector arr(5, 100)
C++ 11 也可以使用初始化列表 std::initializer_list
#include
class Magic{
public:
// 初始化列表构造函数
Magic(std::initializer_list<int> list);
};
Magic magic = {1, 2, 3, 4, 5};
std::vector<int> v = {1, 2, 3, 4, 5};
void foo(std::initializer_list<int> list);
foo({1, 2, 3});
struct A{
int a;
double b;
};
A a {1, 1.0};
struct B{
B(int _a, double _b):a(_a), b(_b){}
private:
int a;
double b;
};
B b {2, 2.0};
???
类型别名模板
// 传统
typedef int (*process)(void*) // 定义一个返回类型为int,参数为void*的函数指针类型,名字叫做process
// C++11
using process = int(*)(void*);
// 传统
template<typename T, typename U>
class SuckType;
typedef SuckType<std::vector, std::string> NewType; // 非法,模板
// C++11
using NewType = SuckType<std::vector, std::string>; (模板类使用,T = vector, U = string)
class Base{
public:
int value1_;
int value2_;
Base() {
value1_ = 1;
}
Base(int value) : Base() { // 委托Base()
value2_ = 2;
}
}
class Base{
virtual void foo() final;
};
class SubClass1 final: public Base {
}; // 合法
class SubClass2: public SubClass1 {
}; // 不合法
class SubClass1: public Base{
void foo();
}; // 不合法
class Magic {
public:
Magic() = default; // 显式声明 "使用" 编译器生成构造
Magic& operator=(const Magic&) = delete; // 显式声明 "拒绝" 编译器生成构造
}
enum class new_enum : unsigned int { // 枚举类
value1,
value2 = 100
};
提供了一种类似匿名函数的特性,即需要一个函数,但是又不想费力去命名一个函数的情况下去使用。
语法:
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}
捕获列表 参数的一种类型,因为lambda表达式内部函数体在默认情况下是不能够使用函数外部的变量的,这时候捕获列表可以起到传递外部数据的作用。
// 1. 值传递,前期是变量可以拷贝,在lambda表达式被创建时拷贝
void learn_lambda_func_1() {
int value_1 = 1;
auto copy_value_1 = [value_1] {
return value_1;
};
value_1 = 100;
auto stored_value_1 = copy_value_1();
// stored_value_1 == 1, value_1 == 100;
// 因为copy_value_1在创建时就保存了一份value_1的拷贝。
}
// 2. 引用捕获,引用捕获保存的是引用变化
void learn_lambda_func_2() {
int value_2 = 1;
auto copy_value_2 = [&value_2] {
return value_2;
};
value_1 = 100;
auto stored_value_2 = copy_value_2();
// stored_value_2 == 100, value_2 == 100;
// 因为copy_value_2保存的是引用
}
// 3. 隐式捕获
[] 空捕获列表
[name1, name2, ...] 捕获一系列变量
[&] 引用捕获,让编译器自行推导捕获列表
[=] 值捕获,让编译器自行推导捕获列表
// 4. 表达式捕获,允许捕获的成员用任意的表达式进行初始化,即允许了右值捕获
#include
#include
int main() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
return 0;
}
// 5. 泛型Lambda 使用auto
auto add = [](auto x, auto y) {
return x+y;
};
add(1, 2);
add(1.1, 2.2);
Lambda表达式的本质是一个函数对象,当Lambda表达式的捕获列表为空时,可以作为一个函数指针进行传递,eg:
#include
using foo = void(int);
void functional(foo f) {
f(1);
}
int main() {
auto f = [](int value) {
std::cout << value << std::endl;
};
functional(f); // 函数指针调用
f(1); // lambda 表达式调用
return 0;
}
从上面代码可以看到,可以将函数指针传递调用,或者直接调用函数表达式,在C++11中统一了这些概念,将可以被调用的对象类型,统称为可调用类型。
std::function 通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作。eg:
#include
#include
int foo(int para){
return para;
}
int main() {
std::function<int(int)> func = foo;
int important = 10;
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl; // 10
std::cout << func2(10) << std::endl; // 1+10+10 = 21
}
std::bind / std::placeholder
std::bind 解决的问题:将部分调用参数提前绑定到函数身上成为一个新的对象,然后等参数齐全后,完成调用。eg:
int foo(int a, int b, int c){
...;
}
int main() {
// 将参数1,2绑定到函数foo上,但是使用std::placeholders::_1来为第一个参数占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 1, 2);
// 调用bindFoo时只需要提供第一个参数就可以了
bindFoo(1);
}
int a = 1; // a为左值,1为右值
A getNewA(){return A();}
A a = getNewA(); // a是左值,在当前位置会长久存在,getNewA()返回的是右值,临时对象,复制一份给a之后就会被自动析构
左值引用使用的符号是&,如:
int a = 1; // a是一个左值
int &b = a; // 左值引用
int &b = 1; // 编译错误,左值引用,但是1是一个右值,
右值引用使用的符号是&&,如:
int&& a = 1; // 将 1 取了个别名
int b = 1;
int&& c = b; // 不能将一个左值复制给一个右值引用
A&& a = getNewA(); // getNewA()的返回值是右值(临时变量)
/*getNewA()返回的右值本来在表达式语句结束后,其生命结束,而通过右值引用,该右值又重获新生,其生命期将与右值引用类型变量a的生命期一样,可以理解为将这个临时变量提出来取了个名字。*/
const int& a = 1; // 常量左值引用绑定右值,不会报错
const A& a = getNewA(); // 不会保证报错
将亡值:即将被销毁、却能够被移动的值。可以理解为:临时的值能够识别、同时又能够被移动。
#include
#include
void reference(std::string& str) {...} // str左值
void reference(std::string&& str) {...} // str右值
int main() {
std::string lv1 = "string,"; // lv1左值
std::string&& rv1 = std::move(lv1); // 将lv1暂时变成右值???
const std::string& lv2 = lv1+lv1; // 合法,常量左值引用,记住lv2不可以被修改
std::string&& rv2 = lv1+lv2; // 合法,右值引用延长临时对象生命周期
// rv2是一个左值,右值引用lv1;
}
class MyString {
public:
MyString(const char* cstr = nullptr){
if(nullptr != cstr) {
m_data = new char[strlen(cstr)+1]; // strlen不计算\0
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
// 拷贝构造
MyString(const MyString& str) { // 引用肯定不是空指针
m_data = new char[strlen(str.m_data)+1]; // strlen不计算\0
strcpy(m_data, str.data);
}
// 移动构造
MyString(MyString&& str) : m_data(str.m_data){
str.m_data = nullptr;
}
private:
char* m_data;
};
int main() {
vector<MyString> vec;
vec.push_back(MyString("hello"));
};
MyString 类中有构造函数、拷贝构造函数、移动构造函数,观察后两个的实现,拷贝构造用的是常量左值引用,移动构造用的是右值引用,而在main函数中的 MyString(“hello”) 是一个临时对象,编译器会优先调用移动构造函数。这样就不用调用拷贝构造函数,拷贝一份MyString(“hello”)到vec数组中,然后再调用析构函数。而是直接把MyString(“hello”) 移动到vec中。
另外的例子
MyString str1("hello"); // 左值
MyString str2("world"); // 左值
MyString str3(str1); // 调用拷贝构造函数
MyString str4(std::move(str2)); // str::move将str2转比成右值,调用移动构造函数
void reference(int& v) {std::cout << "左值" << std::endl;}
void reference(int&& v) {std::cout << "右值" << std::endl;}
template <typename T>
void pass(T&& v) { //一个声明的右值引用其实是一个左值
std::cout << "普通传参:";
reference(v); // 始终调用 reference(int& )
}
int main() {
std::cout << "传递右值:" << std::endl;
pass(1); // 1是右值, 但输出左值
std::cout << "传递左值:" << std::endl;
int v = 1;
pass(v); // r 是左引用, 输出左值
return 0;
}
就是为了让我们在传递参数的时候,保持原来的参数类型(左引用保持左引用,右引用保持右引用),引入完美转发的概念。使用std::forward
template <typename T>
void pass(T&& v) {
std::cout << "普通传参:";
reference(v);
std::cout << "std::move 传参:";
reference(std::move(v));
std::cout << "std::forward 传参:";
reference(std::forward<T>(v));
}
int main() {
std::cout << "传递右值:" << std::endl;
pass(1);
std::cout << "传递左值:" << std::endl;
int v = 1;
pass(v);
return 0;
}
// 输出结果
传递右值:
普通传参:左值引用
std::move 传参:右值引用
std::forward 传参:右值引用
传递左值:
普通传参:左值引用
std::move 传参:右值引用
std::forward 传参:左值引用
综上可以看出,如果1. 引用折叠后的不管是1还是v都是左值引用;2. std::move(v)后,都是右值引用;3. std::forward(v)将完美转发(传递)原来的引用属性。
对比std::vector:1.std::vector保存在堆内存中,而std::array保存在栈内存中;2. 封装了一些函数
std::array使用时,指定类型和大小(大小必须是常数表达式)。
std::array<int, 4> arr = {1, 2, 3, 4};
std::sort(arr.begin(), arr.end());
std::list 被认为是双向链表,可以实现恒定时间(不用遍历)的插入和擦除,并在两个方向上迭代;
主要缺点就是不能直接访问元素位置,需要线性搜索。
std::forward_list 是单链表,不需要双向迭代的时候,比std::list空间利用率更高。
std::unordered_map / std::unordered_multimap
std::unordered_set / std::unordered_multiset
换句话说,即使存进去是有序的,输出也是无需的。
std::unordered_map<int, std::string> u = {
{1, "1"},
{2, "2"},
{3, "3"}
};
for (const auto it : u)
std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
// 输出
Key:[2] Value:[2]
Key:[3] Value:[3]
Key:[1] Value:[1]
#include
#include
auto get_student(int id)
{
if (id == 0)
return std::make_tuple(3.8, 'A', "张三");
if (id == 1)
return std::make_tuple(2.9, 'C', "李四");
return std::make_tuple(0.0, 'D', "null");
}
int main()
{
auto student = get_student(0);
std::cout << "ID: 0, "
<< "GPA: " << std::get<0>(student) << ", "
<< "成绩: " << std::get<1>(student) << ", "
<< "姓名: " << std::get<2>(student) << '\n';
double gpa;
char grade;
std::string name;
// 元组进行拆包
std::tie(gpa, grade, name) = get_student(1);
std::cout << "ID: 1, "
<< "GPA: " << gpa << ", "
<< "成绩: " << grade << ", "
<< "姓名: " << name << '\n';
}
std::tuple<double, char, std::string, double> t(3.8, 'A', "张三", 2.0);
std::get<std::string>(t);
std::get<double>(t); // 非法, 引发编译期错误
#include
template <size_t n, typename... T>
boost::variant<T...> _tuple_index(size_t i, const std::tuple<T...>& tpl) {
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(T) - 1)
throw std::out_of_range("越界.");
else
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... T>
boost::variant<T...> tuple_index(size_t i, const std::tuple<T...>& tpl) {
return _tuple_index<0>(i, tpl);
}
int i = 1;
std::cout << tuple_index(i, t) << std::endl;
// 1. 合并元组, 使用std::tuple_cat
auto newTuple = std::tuple_cat(get_student(1), std::move(t));
// 2. 遍历元组
# 知道tuple的长度
template <typename T>
auto tuple_len(T &tpl) {
return std::tuple_size<T>::tpl;
}
# 遍历
for(int i = 0; i != tuple_len(new_tuple); ++i)
std::cout << tuple_index(i, new_tuple) << std::endl;
引用计数是为了防止内存泄漏而产生的,基本想法是对于动态分配的对象,进行引用计数,每当增加一次对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
RAII资源获取即初始化技术对于一个对象而言,我们在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间。
C++11 引入智能指针,使用引用计数的概念,自动释放内存。
正则表达式描述了一种字符串的匹配模式,一般用于实现三个功能:
正则表达式 [a-z]+\.txt
[a-z]表示小写字母中任意一个字母,+ 表示多次匹配
.表示匹配单个字符,但是我们这里就要用.越来的含义时,就是用\转义符 \.就表示.
所以[a-z]+\.txt匹配名字为小写字母的.txt文件
c++ 11提供的正则表达式操作是对 std::string 的,用std::regex进行初始化,通过std::regex_match进行匹配,从而产生std::smatch
#include
#include
#include
int main() {
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"}; // 字符串数组
std::regex txt_regex("[a-z]+\\.txt"); // 初始化一个正则表达式
for (const auto &fname: fnames)
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
// 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.`
std::thread用于创建一个执行的线程实例,所以他是一切并发编程的基础,使用时需要包含 头文件
#include
#include
void foo() {...}
int main() {
std::thread t(foo);
t.join;
return 0;
}
std::mutex 初始化一个互斥量, 使用std::lock函数上锁,std::unlock解锁,还有模板类std::lock_guard实现RAII的功能。
void some_operation(const std::string &message) {
static std::mutex mutex_;
std::lock_guard<std::mutex> lock(mutex_);
// ...操作
// 当离开这个作用域的时候,互斥锁会被析构,同时unlock互斥锁
// 因此这个函数内部的可以认为是临界区
}
由于 C++保证了所有栈对象在声明周期结束时会被销毁,所以这样的代码也是异常安全的。无论 some_operation() 正常返回、还是在中途抛出异常,都会引发堆栈回退,也就自动调用了 unlock()
std::unique_lock
std::unique_lock 则相对于 std::lock_guard 出现的, std::unique_lock 更加灵活, std::unique_lock 的对象会以独占所有权(没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权)的方式管理 mutex 对象上的上锁和解锁的操作。所以在并发编程中,推荐使用std::unique_lock
#include
#include
#include
std::mutex mtx;
void block_area() {
std::unique_lock<std::mutex> lock(mtx);
//...临界区
}
int main() {
std::thread thd1(block_area);
thd1.join();
return 0;
}
long long int 至少具备64位的比特数
C++有一套完整的异常处理机制,但是没有接触过,后续总结一下。
C++11将异常的声明简化为以下两种情况:
void may_throw(); // 可能抛出异常
void no_throw() noexcept; // 不可能抛出异常???什么情况不会抛出异常
noexcept修饰的如果抛出异常,编译器会使用std::terminate() 终止程序运行。
原始字符串字面量
传统的C++:C:\\What\\The\\Tranditional
C++11: R"(C:\\What\\The\\Tranditional)";