参考
现代cpp教程
https://changkun.de/modern-cpp/
以下所有代码运行在win10+ VS2019+ cmake+ VS Code的环境下
constexpr将表达式在编译器就计算好,从而运行时为一个常量。这就使得可以指定变量作为数组大小。
#include
int main() {
constexpr int len = 10;
char arr[len];
arr[1] ='c';
std::cout << arr[1] <<std::endl;
return 0;
}
去掉constexpr 关键字,在g++8.1上仍然通过,但是在msvc 2019上不通过。原因是g++将其自动优化了。
可以用来预测参数的类型,在模板中有用。
#include
template<typename T>
bool isInt(T n){
if constexpr(std::is_same<decltype(n), int>::value)
return true;
else
return false;
}
int main() {
std::cout << isInt(5) << std::endl;
std::cout << isInt(3.14) << std::endl;
}
其中if constexpre做了静态的分支判断。这是因为类型信息本来就是静态的(运行时常量)
std::is_same
用法是,先用decltype判断类型,然后用is_same判断类型是否相等。
#include
constexpr int fibonacci(const int n) {
if(n == 1) return 1;
if(n == 2) return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
int main(){
std::cout<<fibonacci(10)<<std::endl;
char a[fibonacci(10)];
}
返回值不仅可以是constexpr,而且该函数还能递归。最后返还的值,可以作为数组大小。
auto是个语法糖,用来自动推断类型。
最简单的用法是这样:推导简单的类型
int main(){
auto i =5;
}
或者这样:推导new返回的指针类型。
int main(){
auto p = new int(2);
}
这就有点像是js中的let了。
还可以这样推导自定义的类型
#include
class A{
int a,b;
};
int main(){
auto pa = new A();
}
简化迭代器冗长写法
#include
#include
#include
int main(){
std::vector<int> vec{1,2,3,4};
//for(std::vector::const_iterator it = vec.cbegin(); it != vec.cend(); ++it) //冗长写法
for(auto it = vec.cbegin(); it != vec.cend(); ++it) //简化写法
std::cout<<*it<<std::endl;
}
std::vector::const_iterator被简化为了auto
简化智能指针冗长写法
#include
#include
int main(){
//std::shared_ptr p = std::make_shared(5); //冗长写法
auto p = std::make_shared<int>(5); //简化写法
std::cout<<p<<std::endl;
}
#include
template<typename T, typename U>
auto add(T x, U y){
return x + y;
}
int main(){
std::cout<<add(3,33)<<std::endl;//自动推导为整型
std::cout<<add(3,3.3)<<std::endl;//自动推导为double型
}
一般来说,在模板中给的typename xxx是个类型,比如int, double之类的。但其实我们还可以直接传递一个具体的参数,比如100。
template <typename T, int BufSize>
//template 为更好的写法。
class buffer_t {
public:
T& alloc();
void free(T& item);
private:
T data[BufSize];
};
int main(){
buffer_t<int, 100> buf; // 100 作为模板参数
}
这里的具体参数100,叫做非类型模板参数。这样有点类似于常规的函数参数了。
实际上,我们可以将其进一步泛化,把int BufSize改为auto BufSize
这样,使用的时候仍然可以给100,而且我们在设计模板的时候无需关心类型。
也是个语法糖,相当于不用定义迭代器了,直接在某个列表内循环(类似于python 的for i in List)
#include
#include
#include
int main() {
std::vector<int> vec = {1, 2, 3, 4};
for (auto it = vec.begin(); it != vec.end(); ++it)//冗长写法
std::cout << *it << std::endl; //冗长写法
for (auto element : vec)//简化写法
std::cout << element << std::endl; //简化写法
}
可见range for直接取值,而不是指针,所以不需要用*解引用。
如果需要修改值的话,那就写为引用。
#include
#include
int main() {
std::vector<int> vec = {1, 2, 3, 4};
for (auto &element : vec)
{
element+=1;
std::cout << element << std::endl;
}
}
也是个语法糖。和python里面的元组类似。
注:需要c++17
#include
#include
auto f() {
return std::make_tuple(1, 2.3, "456");
}
int main() {
auto [x, y, z] = f();
std::cout << x << ", " << y << ", " << z << std::endl;
return 0;
}
就是先把多个值打包成元组,然后接收返回值的时候再解包。
越来越像python了…
原本用typedef yyy xxx的地方都可以换成using xxx = yyy
这样的好处是更加清晰。因为在typedef一个函数对象的时候,会造成心智负担。比如
typedef int (*process)(void *);
using NewProcess = int(*)(void *);
把某个函数对象(其参数为一个void *,其返回值为一个int *)赋予别名process的时候,假如用typedef,就是上面那行。
看起来很不直观。
下面的新写法则简单直观得多。
这是个语法糖。
在写构造函数的时候,往往要重载多个版本。为了让功能一致的部分能够复用,可以采用委托构造。
#include
class Base {
public:
Base() {
std::cout << "in the first constructor" << std::endl;
}
Base(int value) : Base() { // 委托 Base() 构造函数
std::cout << "in the second constructor" << std::endl;
}
};
int main() {
Base a;
Base b(2);
}
输出
in the first constructor
in the first constructor
in the second constructor
也就是调用第二个构造函数的时候,首先调用了第一个构造函数,然后调用第二个。相当于第二个重载的构造函数复用了第一个构造函数的部分,并且加上了自己的部分。这就实现了代码复用。
假如将
Base(int value) : Base() { // 委托 Base() 构造函数
改为
Base(int value) { // 委托 Base() 构造函数
则输出
in the first constructor
in the second constructor
也就是调用第二个构造函数的时候只调用第二个。
这也是个语法糖。
父类有多个版本的构造函数的时候,子类也必须一一对应着写多个版本,这很麻烦。
使用using关键字,可以把父类的构造函数复用到子类中。
原本冗长的写法是这样
#include
class Base {
public:
int value1,value2;
Base() {} //version1
Base(int v1):value1(v1) {}//version2
Base(int v1, int v2):value1(v1),value2(v2) {}//version3
};
class Subclass : public Base {
public:
Subclass():Base() {} //version1
Subclass(int v1):Base(v1) {}//version2
Subclass(int v1, int v2):Base(v1,v2) {}//version3
};
int main() {
Subclass s(1,2);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
}
父类有几个构造函数,子类就要写几个。
但是假如用using关键字,可以直接复用父类的构造函数
#include
class Base {
public:
int value1,value2;
Base() {} //version1
Base(int v1):value1(v1) {}//version2
Base(int v1, int v2):value1(v1),value2(v2) {}//version3
};
class Subclass : public Base {
public:
// Subclass():Base() {} //version1
// Subclass(int v1):Base(v1) {}//version2
// Subclass(int v1, int v2):Base(v1,v2) {}//version3
using Base::Base;
};
int main() {
Subclass s(1,2);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
}
假如Subclass新增了一个成员变量value3,那么只需要再重载一个版本的构造函数就好了。
#include
class Base {
public:
int value1,value2;
Base() {} //version1
Base(int v1):value1(v1) {}//version2
Base(int v1, int v2):value1(v1),value2(v2) {}//version3
};
class Subclass : public Base {
public:
int value3;
// Subclass():Base() {} //version1
// Subclass(int v1):Base(v1) {}//version2
// Subclass(int v1, int v2):Base(v1,v2) {}//version3
using Base::Base;
Subclass(int v1, int v2, int v3):Base(v1,v2), value3(v3) {}
};
int main() {
Subclass s(1,2,3);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
std::cout << s.value3 << std::endl;
}
这也是语法糖。
在以前,只需要在父类指定virtual就表示该函数要在子类中被重写。
然而不需要在子类中的同名函数中给出任何标识(不需要virtual)。这就让虚函数看起来长得和普通函数完全一样。
所以增加一个overide标识,表示该函数是被重写了的虚函数。
这样做除了看上去好看之外,还有一个优点:就是防止不是虚函数的普通函数被重写了。一旦你试图这么做,编译器就会报错。(没错,virtual关键字和是否能被重写毫无关系,即使普通成员函数也能被重写,请看我之前的博客)。
struct Base {
virtual void foo(int);
};
struct SubClass: Base {
virtual void foo(int) override; // 合法
virtual void foo(float) override; // 非法, 父类没有此虚函数
};
报错
使用“override”声明的成员函数不能重写基类成员C/C++(1455)
也是语法糖
它有两个用法
struct Base {
};
struct SubClass final: Base {
};
struct SubSubClass: SubClass {
};
int main() {
}
会报错
error C3246: “SubSubClass”: 无法从“SubClass”继承,因为它已被声明为“final”
struct Base {
virtual void func() final;
};
struct SubClass final: Base {
void func();
};
int main() {
}
会报错
error C3248: “Base::func”: 声明为“final”的函数无法被“SubClass::func”重写
这是语法糖,用来
禁用默认赋值构造函数和默认拷贝构造函数。
以往的做法是把他们设定为private。
很多时候我们都是不允许对象被拷贝或者赋值的,因为会造成内存泄漏(他们都是浅拷贝,一旦析构,指针所指的内容就丢失了。)
class Magic {
public:
Magic() = default; // 显式声明使用编译器生成的构造
Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造
Magic(int magic_number);
};
int main() {
Magic a;
Magic b;
b=a;
}
会报错
error C2280: “Magic &Magic::operator =(const Magic &)”: 尝试引用已删除的函数
这个是重中之重。
其实也可以看作一种语法糖,就是在原地定义了一个函数对象。
最简单的一个例子
#include
auto add = [](auto x, auto y) {//简化的写法
return x+y;
};
template<typename T>//原本的写法
auto old_add(T x, T y) {
return x+y;
}
int main()
{
auto res = add(1, 2);
auto res1 = old_add(1.1, 2.2);
std::cout << res << std::endl;
std::cout << res1 << std::endl;
}
如果只是单纯的把原本的函数换了个写法,这没什么的。lambda表达式的一个精髓在于它可以捕获外部变量。
关键就在于[]。
分两种捕获:值捕获和引用捕获
#include
int main() {
int value = 1;
auto func = [value]() {
return value;
};
auto v = func();
std::cout<<v<<std::endl;
}
打印结果
1
#include
int main() {
int value = 1;
auto func = [&value]() {
value = 2;
};
func();
std::cout<<value<<std::endl;
}
打印结果
2
可以省略value,直接写成[=]或者是[&],前者是值捕获,后者是引用捕获。
值捕获
#include
int main() {
int value = 1;
auto func = [=]() {
return value;
};
auto v = func();
std::cout<<v<<std::endl;
}
打印结果
1
#include
int main() {
int value = 1;
auto func = [&]() {
value = 2;
};
func();
std::cout<<value<<std::endl;
}
打印结果
2
#include
int main() {
int v2 = 2;
auto func = [v1=1, v2]() {
return v1+v2;
};
auto res = func();
std::cout<<res<<std::endl;
}
输出3
#include
int main() {
int v2 = 2;
auto func = [v1=1, &v2]() {
v2 *= 10;
return v1+v2;
};
auto res = func();
std::cout<<res<<std::endl;
}
输出21