Modern Cpp
介绍Cpp 11/14/17 的一些高频的 新的语法和特性。
使用Modern Cpp可使得开发更加便捷,可读性更好,代码看起来更加精简。
在Cpp 11 为我们带来了很多的新特性,使得其是Cpp发展中具有里程碑的版本。
统一的类成员初始化方法和std::initializer_list<T>
在Cpp11 前,需要初始化类的某些成员会非常的繁琐
例如:
//类中具有一个int 数组。
class CppOlder{
public:
CppOlder(){
m_array[0]=0;
m_array[1]=1;
m_array[2]=2;
m_array[3]=3;
}
private:
int m_array[4];
};
但是在Cpp11可以这样写
class ModernCpp {
public:
ModernCpp():m_array{1,2,3,4} {
}
private:
int m_array[4];
};
//或者这样写
//虽然可以在定义的地方可以初始化,但是建议还是在构造函数处初始化,方便维护。
class ModernCpp {
public:
ModernCpp(){
}
private:
int m_array[4]{1,2,3,4};
};
自己实现的类,如何使用这种语法?
通过std::initializer_list<T>
class ModernCpp {
public:
ModernCpp(std::initializer_list<int> list):m_array{1,2,3,4}{
m_vec.insert(m_vec.end(),list.begin(),list.end());
}
void append(std::initializer_list<int> list){
m_vec.insert(m_vec.end(),list.begin(),list.end());
}
private:
std::vector<int> m_vec;
int m_array[4];
};
// ModernCpp modernCpp{1,2,3,4};
// modernCpp.append({1,2,3,4});
// m_vec为 1 2 3 4 1 2 3 4
一个稍微复杂,但看起来很有用的示例
enum class JsonType {
JsonInt = 0,
JsonString,
JsonDouble,
JsonFloat
};
struct JsonNode {
JsonNode(const char *key, const char *value) :
m_type(JsonType::JsonString),
m_key(key),
m_value(value)
{
std::cout << "JsonString" << std::endl;
}
JsonNode(const char *key, const double value) :
m_type(JsonType::JsonString),
m_key(key),
m_value(std::to_string(value))
{
std::cout << "JsonString" << std::endl;
}
JsonType m_type;
std::string m_key;
std::string m_value;
};
class json {
public:
static json &array(std::initializer_list<JsonNode> nodeList) {
m_json.m_jsonNodes.clear();
m_json.m_jsonNodes.insert(m_json.m_jsonNodes.end(), nodeList.begin(), nodeList.end());
return m_json;
}
private:
std::vector<JsonNode> m_jsonNodes;
static json m_json;
};
json json::m_json;
//main.cpp
json array = json::array({
{"hello", "world"},
{"hello1", "world"},
{"hello2", "world"}
});
final、override、=delete 、=default
这些关键字是Cpp11新增加的一些比较有意义的关键字和语法。
final
被final修饰的类表明,这个类无法被继承。
当我们试图去继承这个类的话,会出现编译错误。
class TestFinal final{
public:
TestFinal() = default;
~TestFinal() = default;
};
class TestFinal1:public TestFinal{
};
//编译错误
//error:: 25: base 'TestFinal' is marked 'final'
override
当一个类成员函数被virtual关键字修饰的时候,表明其子类重写这个函数。
override关键字,用于告诉编译器,这个类成员函数是重写父类的方法,编译器会对其进行相应的检查。
在Cpp11前,在子类中重写父类的方法时,无论是否添加了virtual关键字,都无法显示的表面子类是否重写了该方法。
因为,我们在重写方法是,可能将其函数签名写错,这时编译器并不会检查这类错误,因为子类也可以是另一个子类的父类。
如果函数签名写错了,就定义了一个新的函数出来,而不是重写父类的方法。
class TestOverride{
public:
virtual void helloWorld(std::string str){ std::cout<<str<<std::endl;}
};
class TestOverrideInherit:public TestOverride{
public:
void helloWorld() override{}
};
//编译错误,函数签名正确
//error: non-virtual member function marked 'override' hides virtual member function
//改成这样后,编译通过。
class TestOverrideInherit:public TestOverride{
public:
void helloWorld(std::string str) override{}
};
=default
当我们定义了一个类,如果我们没有编写构造函数,拷贝构造,析构函数,operator=,编译器会帮我们生成默认的函数。
但是当我们只写出来声明时,编译就会报错。当我们写了一个带有参数的构造,那么编译器就不会帮我们生成默认的函数。
class TestDefault{
};
int main(){
TestDefault td;
}
//以上代码是可以编译通过的。
class TestDefault{
public:
TestDefault(int i){};
};
int main(){
TestDefault td;
}
//当我们添加了一个有参的构造函数时,编译器不会再帮我们生成默认的构造函数了。
//编译错误:
//error: no matching constructor for initialization of 'TestDefault'
class TestDefault{
public:
TestDefault() = default;
TestDefault(int i){};
};
int main(){
TestDefault td;
}
//显示的告诉编译器帮我们生成默认的构造函数。
//在一些不需要进行大量初始化动作的类中,这样写可以简化代码。
=delete
告诉编译器不要帮我生成默认的函数。
在Cpp11以前,我们想要实现一个类无法被拷贝,需要将其拷贝构造函数、operator=()都设置为私有的函数这样就实现了禁止拷贝。
class TestDelete{
public:
TestDelete() = default;
TestDelete(const TestDelete& other) = delete;
TestDelete& operator=(const TestDelete& other) = delete;
};
int main(){
TestDelete tDelete;
TestDelete tDelete1 = tDelete;
TestDelete tDelete2(tDelete);
}
//编译错误:
//error: call to deleted constructor of 'TestDelete'
auto关键字、Range-based循环
这两个新的特性和语法,在平时的开发中很常用。
auto关键字
Cpp11中的auto和Cpp98 auto 完全不一样。
在Cpp98中,auto关键字表明该变量具有自动生命周期,但是完全是多余的,因为默认变量就具有自动生命周期。
在Cpp11中,使用auto关键字来自动推导出变量的类型。
auto testAutoInt = 1;
auto testAutoDouble = 1.1;
auto testChar = 'c';
auto p = new ClassA();
但是在一些简单的数据类型,尽量还是不要使用auto。
尽量使用在一些复杂的数据类型中,例如 如下代码:
std::vector<std::string> vec;
std::vector<std::string>::iterator iter = vec.begin();
auto iter = vec.begin(); //使用auto看起来简洁很多。
但是在使用auto时需要注意:
- 定义变量时必须初始化。
- 如果表达式是引用,这将除引用语义
int a = 12;
int &b = a;
auto c = b;
b = 20;
std::cout<<a<<std::endl; //a = 20;
- 如果表达式是为const,这将去除const语义
const int a=2;
auto b = a;
b = 3;
std::cout<<a<<std::endl; //a = 3;
- 使用auto时,添加&符号,则保留了const语义
const int a = 2;
auto &b = a;
b = 3; //error:Cannot assign to variable 'b' with const-qualified type 'const int &'
Range-based 循环
在没有Range-based循环的语法前,大多数遍历的时候只能通过下标去完成遍历,或者是迭代器的增加去完成遍历。
int a[5]={1,2,3,4,5}; //Cpp11 列表初始化。
for (int i = 0; i < 5; ++i) {
std::cout<<a[i]<<std::endl;
}
//通过迭代器遍历
std::vector<int> vec{1, 2, 3, 4, 5};
std::vector<int>::iterator iter;
for (iter = vec.begin(); iter != vec.end(); iter++) {
std::cout << *iter << std::endl;
}
通过Range-based循环,可以很大程度上简化上面的代码
std::vector<int> vec{1, 2, 3, 4, 5};
for (auto item : vec) {
std::cout << *iter << std::endl;
}
//item 是vec中元素的一个副本。
for (auto &item : vec) { //添加引用就可以修改对应的元素。
std::cout << *iter << std::endl;
}
for (const auto &item : vec) { //添加引用可以不拷贝元素,并通过const来避免被修改。
std::cout << *iter << std::endl;
}
Cpp17 结构化绑定
使用结构化绑定,更加方便的返回多值,并且不用定义结构体。
使得代码看起来更简洁明了,开发也更加方便。
std::tuple<std::string, int> Cpp17StructuredBinding() {
return std::tuple<std::string, int>("小明", 18);
}
auto [name, age] = ModernCpp::Cpp17StructuredBinding();
使用限制:
- 无法使用constexpr 修饰
constexpr auto [name, age] = ModernCpp::Cpp17StructuredBinding(); //编译错误
- 无法使用static 修饰
static auto [name, age] = ModernCpp::Cpp17StructuredBinding(); //编译错误
STL 容器新增加的实用方法
STL中新增加的一些,提升效率的方法。