当前位置:   article > 正文

C++实战_c++ 实战

c++ 实战


1. 预定义宏

  • __cplusplus:C++语言的版本号。如:202002(c++20),201703(c++17),201402,201103
  • __DATE__ :预处理时的日期
  • __FILE__ :源文件名
  • __LINE__ :源文件行号
  • __has_include:是否存在某个可包含的文件
  • __cpp_modules:是否支持模块机制
  • __cpp_decltype:是否支持decltype特性
  • __cppdecltype_auto:是否支持decltype(auto)特性
  • __cpp_lib_make_unique:是否提供函数make_unique()
  • __GNUC__:检查GCC的版本
  • __SSE4_2__ 、__x86_x64:检查CPU指令集

2.属性

  • C++11之前非官方的“编译指令”:__attribute__(GCC)、__declspec(VS)
  • C++11支持的属性:noreturn、carries_dependency
    语法:用“[[…]]”标识
    示例:
[[noreturn]]  //属性标签,函数绝不会返回任何值
int func(bool flag)
{
 throw std::rutime_error("xxx");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • C++14新增属性:deprecated(废弃某段代码,不鼓励使用)

  • C++17/20新增属性:

    • nodiscard:显示声明不允许忽略函数返回值
    • maybe_unused:显示标记某段代码暂时不用,但保留,因为将来可能会用
    • fallthrough:仅用于switch语句
    • likely/unlikely:标记某段代码路径更可能/更不可能,只是编译器优化
  • 非标准扩展属性(GCC):

    • const:标记函数时无副作用的常量函数,让编译器积极优化
    • constructor:函数会在main()之前执行
    • destructor:函数会在main()之后执行
    • always_inline:要求编译器强制内敛函数,效果比inline关键字更强。
    • hot:标记“热点”函数,要求编译器更积极地优化。
    • 示例:
[[gnu::constructor]]
void first_func(){
    printf("before main()\n");
}
  • 1
  • 2
  • 3
  • 4

3.断言

  • 动态断言
    assert:断言一个表达式必定为真,否则输出错误消息,然后调用abort()终止程序。只会在调试版本里生效(之前未定义NDEBUG)。
  • 静态断言
    static_assert:C++的关键字,在编译阶段检测各种条件的断言,在编译阶段计算表达式的值,如果是false就会报错,导致编译失败。
    C++11中要求有两个参数,即static_assert(cond,msg),第二个参数是警告消息。C++14可以不提供参数。
    智能化看到编译时的常量和类型,不能看到运行时的变量、指针、内存数据等。
    类型检查用的模版元函数:
static_assert(is_integral_v<T>); //断言T是整数类型
static_assert(is_pointer_v<T>); //断言T时指针类型
static_assert(is_default_constructible_v<T>); //断言T有默认构造
  • 1
  • 2
  • 3

4.面向对象编程

  • 面向对象编程的关键是抽象和封装,而不是继承和多态。应当尽量少用继承和多态。
  • 继承的深度不要超过3层。如果超过,考虑用组合关系替代继承,或者改用模版和泛型。
  • 设计类接口的时候,尽量简单、短小精悍,只负责单一的功能。
  • 避免嵌套类,应使用命名空间,把内部类提到外边,歼敌原来类的耦合度和复杂度。

5.转型操作符、委托构造及静态成员初始化

  • 转型操作符
class DemoClass{
puublic:
    operator bool(){...}//转型为bool
};
  • 1
  • 2
  • 3
  • 4
  • 委托构造
    在一个构造函数中直接调用另一个构造函数。
  • 静态成员变量初始化
  • const静态成员变量,C++允许直接在声明的时候初始化。非const静态成员变量,必须在实现文件里单独再初始化(因为需要非配唯一的存储空间)。
    C++17中通过inline关键字声明内联变量可以初始化非const静态成员变量。
class DemoClass{
puublic:
    static const int x = 10;
    //static std::string prefix = "xxx"; //无法通过编译
    inline static std::string prefix = “xxx”; //C++17编译正常 
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6.型别推导

  • C++14新增了字面量后缀“s”来表示标准字符串,所以可以用auto str = "xxx"s;的形式直接推导出std::string类型。
auto str = "hello";
auto str1 = "hello"s;
  • 1
  • 2
  • 类成员变量初始化不允许使用auto类型推导,但静态成员变量允许使用auto类型推导。
  • C++17中的auto的结构化绑定
tuple x{1,"x"s,0.1};
auto [a,b,c] = x;//结构化绑定,x中的值分别赋值给a,b,c

std::map<int,std::string> map = {{1,"abc"s},{2,"qwe"s}};
for(auto& [k,v] : map){
  std::cout << k << "=>" << v << std::endl;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

7.const、mutable、volatile、consexptr

  • std::as_const():无条件吧变量转为常量引用。
  • mutable:mutable修饰的成员变量可以再const修饰的函数中修改
  • volatile:告诉编译器不要优化
//如要获取实时温度时需要连续赋值
int a = 1;
a = 2;
a = 3;  //编译器优化后为 a = 3;

volatile int a = 1;
a = 2; 
a = 3; // 不优化
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • consexptr:定义编译期常量,能够用于编译期计算。

8.异常

  • 异常只能按照catch块在代码里的顺序依次匹配,而不会去寻找最佳匹配。因此最好只用一个catch块。
  • catch块的捕获变量使用&,避免异常对象复制的代价。
  • function-try:
void some_function()//函数名之后直接写try块
try{
//函数体
...
}
catch(...){
...   //catch块与函数体同级并列
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 谨慎使用异常,以下情况请使用异常:
    • 不允许被忽略的错误
    • 极少情况下才会发生的错误
    • 严重影响正常流程,很难恢复正常状态的错误
    • 本地无法处理,必须“穿透”调用栈,传递到上层才能被处理的错误
      比如构造函数、读写文件可以使用异常。socket通信失败频率太高,不要使用异常,使用错误码检查更好。
  • noexcept 可以当作编译期运算符,指定在某个条件下才不会抛出异常,常用的noexcept其实相当于noexcept(true)。
  • throw可以抛出任何类型的异常,但最好使用标准库里定义的exception类。

9.lambda式

  • C++里每个lambda式都是一个独特的类型
  • lambda式的泛型编程
auto f = []<typename T>(const T& x){
    static_assert(is_integral_v<T>);
    return x + x;
}
  • 1
  • 2
  • 3
  • 4

10.内联名字空间和嵌套名字空间

  • 内联名字空间(C++11)
inline namespace tmp{
    auto x = 0L;
    auto str = "hello";
}
cout << x << endl.   //可以直接使用内部成员,不需要空间限定
cout << tmp::str << end;//也可加名字空间
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 嵌套名字空间(C++17)
    以“::”来分隔多个名字空间
namespace a::b::c::{
...
}
  • 1
  • 2
  • 3

11. if/switch语句初始化(C++17)

if(init;cond){

}

vector<int> v{1,2,3};
if(auto pos = v.end();!v.empty()){

}
//多线程编程锁定互斥量
if(scoped_lock g;tasks.empty()){

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

12. 二进制字面值与数字分位符(c++14)

  • 0b/0B 用来直接写二进制数字
  auto x = 0b11010010;
  auto x = 010; //八进制
  • 1
  • 2
  • 数字分位符 用单引号" ’ "来分组
auto a = 0b1011'0101;
  • 1

13. weak_ptr的另一个用途

weak_ptr的另一个用途——让类正确的自我创建shared_ptr:对象内部用weak_ptr来保管this指针,然后调用lock()获取shared_ptr();

  • 辅助类 enable_shared_from_this
    需要自我管理的类必须以继承的方式使用它,之后就可以用成员函数shared_from_this()创建shared_ptr,或者调用成员函数weak_from_this()创建weak_ptr。
  • 示例
class SharedSelf final : public std::enable_shared_from_this<SharedSelf>{

};
auto ptr1 = make_shared<SharedSelf>();  //调用工厂函数创建智能指针
assert(ptr1.use_count() == 1);

auto ptr2 = ptr1->shared_from_this();  //正确获得共享指针
assert(ptr2.use_count() == 1);

auto ptr3 = ptr1->weak_from_this();    //也可以获得弱指针
assert(ptr3.use_count() == 1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

14 字符串

  • std::string 中c_str()和data()的区别:两个函数都返回const char*指针,c_str()必定会在末尾添加一个“\0”,data()在c++11之后才等同于c_str()。
  • 原始字符串(c++11)
    原始字符串不会对字符串里的内容做任何转换,完全保持原始风貌。
    • 示例: auto str = R"(nier:automata)";
    • 引号+圆括号如何处理?
      在圆括号的两边加上做多16个字符的特别界定符。
      示例:auto str = R"==(R"(xxx)")=="; //原样输出R"(xxx)"
  • 字符串转换函数
    • 将字符串转换为整数:stoi() / stol() / stoll()
    • 将字符串转换为浮点数:stof() / stod
    • 把整数、浮点数转换成字符串:to_string()
  • 字面值后缀
    C++14新增了一个字面值后缀“s”,表示字符串类型。使用声明的时候可以利用自动类型推导,也可以省去声明临时字符串变量的麻烦,效率也会更高。需要打开名字空间using namespace std::literals;
  • 字符串视图
    C++17新增了新的字符串类string_view,内部只保存了一个指针和长度。
    • 内部使用了常量指针,所以是一个只读视图,只能查看字符串而无法修改,相当于“const string&”。
    • 因为使用了字符指针,可以从C字符串构造,没有“const string&”的临时对象创建操作。
    • 因为使用的是指针,要当心引用的内容可能会失效。
    • string_view是一个只读视图,不能保证字符串末尾一定是NULL,无法提供成员函数c_str()。不能把它用于C函数传参。
    • 也可以用后缀“sv”来表示string view,直接用auto来推导类型,也需要打开名字空间std::literals,如:auto sv3 = "view it"sv;
    • string_view提供了很多和string同名的函数,如:empty()/size()/front()/back()/find()等。string_view用在只读、弱引用场合。
    • string_view不能修改字符串内容,但是可以调整它内部的指针和长度,如:
    string_view sv {"god of war"s};
    assert(sv.substr(4,2) == of);    //取子串
    sv.remove_prefix(3);             //删除前缀  
    assert(sv == " of war");
    sv.remove_suffix(4);             //删除后缀
    assert(sv == " of");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 字符串格式化—— format(C++20)
    • 格式占位符的基本形式:{序号:格式标志}
    < : 数据左对齐
    > : 数据右对齐
    + : 为数字添加正负号标记
    - : 为数字添加正“-”标记,正数无标记
    空格:为数字添加正“-”标记,正数前加空格  //?
    b : 格式化二进制整数
    d : 格式化十进制整数
    o : 格式化八进制整数
    x/X : 格式化十六进制整数
    # : 非十进制数字显示“0b” “0o” “0x”前缀
    数字: 格式化输出的宽度
    
    format("{:>10}", "hello"); //右对齐,10个字符的宽度
    format("{:04}, {:+04}", 100L,88); //指定填充和宽度,默认是十进制
    format("{0:x}, {0:#X}",100L); //格式化为十六进制
    format("{:04o}, {:04b}",7,5); //格式化为八进制/二进制,宽度是4
    format("{{xxx}}");
    //输出
         hello
    0100,  +088
    64, 0X64
    0007, 0101
    {xxx}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

15 正则表达式

15.1 正则表达式对象

正则表达式主要用到两个类regex和smatch。

  • regex 表示一个正则表达式,是basic_regex的特化形式。
  • smatch 表示正则表达式的匹配结果,是match_results的特化形式

创建正则表达式的时候可以传递一些特殊标志,用于控制正则的处理过程。这些标志位于名字空间std::regex_constantes中,如:

  • icase 匹配时忽略大小写
  • optimize 要求尽量优化正则表达式,但会增加正则表达式对象的构造时间
  • ECMAScript 使用ECMAScript兼容语法,这也是默认语法
  • awk/grep/egrep 使用awk/grep/egrep语法
    示例:
using namespace std::regex_constants;
regex reg1{"xyz",icase | oprimize}; //忽略大小写且尽量优化
  • 1
  • 2

15.2 正则表达式算法

  • regex_match 完全匹配一个字符串
  • regex_search 在字符串里查找一个正则匹配,只要找到一个符合的子串就行。
  • regex_replace 先正则查找再替换,原字符串不改动,返回新字符串

15.3 正则匹配

regex_match的三个参数重载形式中,第二个参数存储匹配结果,匹配结果中第0号元素是整个匹配串,其他的则是子表达式匹配串。(子表达式就是正则表达式中”()“括起的部分)。regex_match匹配的时候需要注意,如果想要获取捕获结果,那么目标字符串不能是临时对象,因为匹配结果需要引用字符串,而临时变量在函数调用后就消失了,会导致引用无效。
$N 引用匹配的子表达式,N表示子表达式的序号
$& 引用整个匹配结果
示例:

std::cout << std::regex_replace("hello mike",std::regex(R"((\w+)\s(\w+))"),"$2-says-$1($&)"); //mike-says-hello(hello mike)
  • 1

16 标准容器

16.1 有序容器

定义容器的时候必须要指定key的比较函数。这个函数通常默认为less,表示小于关系。

template<
    class key,
    class Compare = std::less<key>
> class set;
template<
    class key,
    class T,
    class Compare = std::less<key>
> class map;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

自定义类型不支持比较函数,作为容器的key时需要重载比较操作符“<”,或者自定义模板参数

    auto comp = [](auto&& a,auto&& b){
        return a > b;
    };
    set<int,decltype(comp)>  gs(copm);
  • 1
  • 2
  • 3
  • 4

应用场合:如果要求实时插入排序,选择set或map,否则选择vector,全部数据掺入完成后再一次性排序。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/1009057
推荐阅读
相关标签
  

闽ICP备14008679号