关于现代C++的学习总结(1)

1 生命周期和编程范式

C++生命周期包括:编码、预处理、编译和运行。尽量把BUG消除在前三个阶段。C++语言的五种范式:面向对象、泛型编程、函数式编程、模板元编程和面向过程。面向过程和面向对象是最基本的范式。

2 宏定义和条件编译

编译是预处理之后的阶段,输入的是经过预处理后的源码,输出是二进制可执行文件,可以用一些关键字或者指令改变编译器的行为。属性(attribute)就是给变量、函数、类等“贴”上一个编译阶段的“标签”,方便编译器识别处理。 C++11 里只定义了两个属性:“noreturn”和“carries_dependency”。C++14 增加了一个比较实用的属性“deprecated(废除的含义)”,用来标记不推荐使用的变量、函数或者类。

[[noreturn]]              // 属性标签
int func(bool flag)       // 函数绝不会返回任何值
{
    throw std::runtime_error("XXX");
}

[[deprecated("deadline:2020-12-31")]]      // C++14 or later,括号里的为编译标签
int old_func();

静态断言(static_assert)就是在编译阶段写程序,与动态断言assert不同的是,static_assert是一个关键字,而不是一个宏,且只在编译时生效。

//静态断言,传入的模板参数必须大于0
template<int N>
struct fib
{
    static_assert(N >= 0, "N >= 0");

    static const int value =
        fib<N - 1>::value + fib<N - 2>::value;
};
//在泛型编程的时候,结合标准库里的“type_traits”来判断模板参数T的类型
static_assert(
  is_integral<T>::value, "int");

static_assert(
  is_pointer<T>::value, "ptr");

static_assert(
  is_default_constructible<T>::value, "constructible");

static_assert 运行在编译阶段,只能看到编译时的常数和类型,看不到运行时的变量、指针、内存数据等,是“静态”的。

//错误用法
char* p = nullptr;
static_assert(p == nullptr, "some error.");  // 错误用法

3 C++设计好的类的原则:① 思想上抽象与封装;②实现上少用继承(继承层数不要超过两层)和虚函数,类尽量简单、“短小精悍”,只负责单一的功能;③避免使用嵌套类,应该定义一个新的名字空间,把内部类都“提”到外面,降低原来类的耦合度和复杂度。

编码准则:①积极使用“final”关键字,显式地禁用继承,防止其他人有意或者无意地产生派生类。

class DemoClass final    // 禁止任何人继承我
{ ... };

② 在必须使用继承的场合,建议只使用 public 继承,避免使用 virtual、protected。当到达继承体系底层时,要及时使用“final”,终止继承关系。

class Interface        // 接口类定义,没有final,可以被继承
{ ... };           

class Implement final : // 实现类,final禁止再被继承
      public Interface    // 只用public继承
{ ... };

③ 现代C++六大基本函数中比较重要的构造函数和析构函数,应该用“= default”的形式,明确地告诉编译器自己实现和优化。想要显示禁用六大函数则应使用“= delete”。

class DemoClass final 
{
public:
    DemoClass() = default;                              // 明确告诉编译器,使用默认实现
   ~DemoClass() = default;                              // 明确告诉编译器,使用默认实现

    DemoClass(const DemoClass&) = delete;              // 禁止拷贝构造
    DemoClass& operator=(const DemoClass&) = delete;   // 禁止拷贝赋值
};

④ C++ 有隐式构造和隐式转型的规则,如果类里有单参数的构造函数,或者是转型操作符函数,为了防止意外的类型转换,保证安全,就要使用“explicit”将这些函数标记为“显式”。

class DemoClass final 
{
public:
    explicit DemoClass(const string_type& str)  // 显式单参构造函数
    { ... }

    explicit operator bool()                  // 显式转型为bool
    { ... }
};

⑤ 为了初始化成员,避免写init()函数,使用委托构造。委托构造就是一个构造函数直接调用另一个构造函数。

class DemoDelegating final
{
private:
    int a;                              // 成员变量
public:
    DemoDelegating(int x) : a(x)        // 基本的构造函数
    {}  

    DemoDelegating() :                 // 无参数的构造函数
        DemoDelegating(0)               // 给出默认值,委托给第一个构造函数
    {}  

    DemoDelegating(const string& s) : // 字符串参数构造函数
        DemoDelegating(stoi(s))        // 转换成整数,再委托给第一个构造函数
    {}  
};

⑥ 成员变量初始化。在 C++11 里,你可以在类里声明变量的同时给它赋值,实现初始化。

class DemoInit final                  // 有很多成员变量的类
{
private:
    int                 a = 0;        // 整数成员,赋值初始化
    string              s = "hello";  // 字符串成员,赋值初始化
    vector<int>         v{1, 2, 3};   // 容器成员,使用花括号的初始化列表
public:
    DemoInit() = default;             // 默认构造函数
   ~DemoInit() = default;             // 默认析构函数
public:
    DemoInit(int x) : a(x) {}         // 可以单独初始化成员,其他用默认值
};

⑦ 类型别名。C++11 扩展了关键字 using 的用法,增加了 typedef 的能力,可以定义类型别名。

class DemoClass final
{
public:
    using this_type         = DemoClass;          // 给自己也起个别名
    using kafka_conf_type   = KafkaConfig;        // 外部类起别名

public:
    using string_type   = std::string;            // 字符串类型别名
    using uint32_type   = uint32_t;              // 整数类型别名

    using set_type      = std::set<int>;          // 集合类型别名
    using vector_type   = std::vector<std::string>;// 容器类型别名

private:
    string_type     m_name  = "tom";              // 使用类型别名声明变量
    uint32_type     m_age   = 23;                  // 使用类型别名声明变量
    set_type        m_books;                      // 使用类型别名声明变量

private:
    kafka_conf_type m_conf;                       // 使用类型别名声明变量
};

4 自动类型推导

① auto。auto 总是推导出“值类型”,绝不会是“引用”;auto 可以附加上 const、volatile、*、& 这样的类型修饰符,得到新的类型。

auto        x = 10L;    // auto推导为long,x是long

auto&       x1 = x;      // auto推导为long,x1是long&
auto*       x2 = &x;    // auto推导为long,x2是long*
const auto& x3 = x;        // auto推导为long,x3是const long&
auto        x4 = &x3;    // auto推导为const long*,x4是const long*

C++14 里,auto 还能够推导函数返回值。

auto get_a_set()              // auto作为函数返回值的占位符
{
    std::set<int> s = {1,2,3};
    return s;
}

② decltype。decltype 不仅能够推导出值类型,还能够推导出引用类型,也就是表达式的“原始类型”。

int x = 0;          // 整型变量

decltype(x)     x1;      // 推导为int,x1是int
decltype(x)&    x2 = x;    // 推导为int,x2是int&,引用必须赋值
decltype(x)*    x3;      // 推导为int,x3是int*
decltype(&x)    x4;      // 推导为int*,x4是int*
decltype(&x)*   x5;      // 推导为int*,x5是int**
decltype(x2)    x6 = x2;  // 推导为int&,x6是int&,引用必须赋值

C++14 增加了一个“decltype(auto)”的形式,既可以精确推导类型,又能像 auto 一样方便使用。

int x = 0;            // 整型变量

decltype(auto)     x1 = (x);  // 推导为int&,因为(expr)是引用类型
decltype(auto)     x2 = &x;   // 推导为int*
decltype(auto)     x3 = x1;   // 推导为int&

定义类的时候,由于auto 被禁用了,可以使用 decltype 搭配别名任意定义类型,再应用到成员变量、成员函数上,变通地实现 auto 的功能。

class DemoClass final
{
public:
    using set_type      = std::set<int>;  // 集合类型别名
private:
    set_type      m_set;                   // 使用别名定义成员变量

    // 使用decltype计算表达式的类型,定义别名
    using iter_type = decltype(m_set.begin());

    iter_type     m_pos;                   // 类型别名定义成员变量
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泽箬酱咧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值