跟随b站课程爱编程的大丙学习c进行记录1原始字符串字面量它的主要作用让字符串里的反斜杠、双引号和换行更容易书写避免大量转义。基本语法是R(字符串内容)std::string a F:\\robot\\config\\motor.yaml; std::string b R(F:\robot\config\motor.yaml);输出F:\robot\config\motor.yaml#include iostream #include string using namespace std; int main() { int motor_id 1; string path R(F:\robot\motor.yaml); cout Motor ID: motor_id \n; cout path \n; return 0; }输出2constexprconstexpr变量初始化值必须满足编译期常量表达式的要求。constexpr函数满足条件时可以在编译期计算也可以在运行期调用。先分清“编译期”和“运行期”编译期是把代码编译成程序的阶段运行期是程序启动后执行代码的阶段。int a 6; // 数字 6 不需要运行程序就能知道 int b; std::cin b; // b 的值需要等用户输入后才知道constexpr关心的就是这个值能不能提前确定理解const和constexpr变量const表示初始化后不能修改constexpr变量在此基础上还要求初始化值满足编译期常量表达式的条件。constexpr int count 6; // 正确 constexpr int total count * 2; // 正确结果为 12如果值来自用户输入int number; std::cin number; const int a number; // 正确得到值后不再修改 // constexpr int b number; // 错误编译期不知道 number注意const int a 6;也可以是编译期常量。区别是const不保证而constexpr明确要求。理解constexpr函数constexpr int square(int x) { return x * x; }它可以用于编译期计算constexpr int result square(5); // 必须在常量表达式中得到 25也可以接受运行期的值int number; std::cin number; int result square(number); // 正确运行期计算**函数写了constexpr不代表每次调用都在编译期执行。**要看传入的值和调用的使用场景。放到函数模板中含义不变模板负责让同一套代码支持不同类型templatetypename T constexpr T square(T x) { return x * x; }constexpr int a square(3); // T 是 int得到 9 constexpr double b square(1.5); // T 是 double得到 2.25这里template负责类型通用constexpr负责允许满足条件时的编译期计算。模板在编译期处理不代表函数调用一定在编译期执行。放到构造函数中是为了支持编译期初始化对象构造函数负责给对象的成员设置初始值struct Point { int x; int y; constexpr Point(int a, int b) : x(a), y(b) { } };其中: x(a), y(b)表示用a初始化x用b初始化y。constexpr Point p(3, 4); // 在编译期初始化对象 constexpr int value p.x; // 得到 3这个构造函数也能创建普通对象int number; std::cin number; Point p(number, 4); // 正确运行期初始化对象构造函数上的constexpr提供编译期初始化的能力声明对象时的constexpr则要求这次初始化必须满足编译期条件。3auto自动推导auto的作用是让编译器根据初始化表达式推导变量的类型。类型确定后就不会再改变。auto number 10;这里相当于int number 10;3.1const修饰指针指向的对象会保留int number 10; const int* p number; auto q p; // q 的类型是 const int*const int*表示不能通过这个指针修改它指向的对象。// *q 20; // 错误不能通过 q 修改 number q nullptr; // 正确q 自己可以改变指向3.2const修饰指针本身不保留const修饰指针本身普通auto不保留注意const的位置变了int number 10; int* const p number; auto q p; // q 的类型是 int*int* const表示指针p自己不能改变指向。p不能修改指向q可以修改指向// p nullptr; // 错误p 自己不能修改 q nullptr; // 正确q 自己可以修改原指针类型auto q p;推导出的类型const int*const int*int* constint*const int* constconst int*3.3 autoconst int number 10; const int r number;分别使用auto和autoauto a r; // int复制一个值 auto b r; // const int继续引用原对象a 20; // 正确a 是独立的、可以修改的 int // b 20; // 错误不能通过 b 修改原对象决定是否按引用推导的是声明中的auto不是右边的变量原本是不是引用。auto会根据被引用对象保留限定int x 10; const int y 20; auto a x; // int auto b y; // const int3.4不能使用auto的场景场景错误写法原因普通局部变量没有初始化值auto x;没有依据无法推导类型同一条声明推导出不同类型auto a 1, b 2.5;一个推导为int另一个为double普通函数的参数〔C17〕void show(auto x);C17 不支持这种普通函数参数写法普通非静态数据成员struct Motor { auto id 1; };此处必须明确成员类型直接代替容器元素类型std::vectorauto values;必须提供具体元素类型不能声明数组auto arr[3]{1,2,3}原生数组应明确写出元素类型3.5常用的场景第一种迭代器类型较长时。你已经学过map可以比较下面两种写法std::mapint, std::string names; std::mapint, std::string::iterator it1 names.begin(); auto it2 names.begin();it1和it2的类型相同。这里使用auto能省去冗长的类型名称。第二种范围for循环。std::vectorstd::string names{joint1, joint2, joint3}; for (const auto name : names) { std::cout name \n; }这里的name是const std::string可以读取元素也避免了每次复制字符串。根据目的选择写法循环写法含义for (auto value : values)每次复制一个元素for (auto value : values)直接访问元素可以修改for (const auto value : values)直接访问元素通过该引用只读第三种保存 Lambda 表达式。后面学习回调函数时会经常看到auto square [](int x) { return x * x; }; std::cout square(3); // 输出 9Lambda 对象的具体类型由编译器生成使用auto很方便。4decltypedecltype负责确定类型右侧初始化表达式负责提供值或决定引用绑定谁。4.1基本用法取得类型不计算数值decltype(表达式)会分析表达式的类型但不会实际执行其中的计算。int a 10; decltype(a) b 99; decltype(a 3.14f) c 52.13f; decltype(a 1.2 * c) d 520.1314;结果如下所示int b 99; float c 52.13f; double d 520.1314;4.2直接写变量名取得变量声明时的类型当decltype中直接写变量名时会取得这个变量声明的类型包括其中的const和引用。int x 99; const int y x; decltype(x) a1 x; decltype(y) b1 x;相当于int a1 x;const int b1 x;4.3函数调用函数返回方式对应调用的decltype结果返回intint返回intint返回intint返回const intconst intint n 100; int func_int(); int func_int_r(); int func_int_rr(); const int func_cint(); const int func_cint_r(); const int func_cint_rr(); const Test func_ctest(); decltype(func_int()) a2 0; // int decltype(func_int_r()) b2 n; // int decltype(func_int_rr()) c2 0; // int decltype(func_cint()) d2 0; // int decltype(func_cint_r()) e2 n; // const int decltype(func_cint_rr()) f2 0; // const int decltype(func_ctest()) g2 Test(); // const Testdecltype(func_cint()) d2 0; // int decltype(func_ctest()) g2 Test(); // const Test对于这里的内置类型int按值返回时调用表达式不会保留顶层const而类类型const Test在这个例子中会保留。4.4表达式是一个左值或者被括号( )包围使用 decltype推导出的是表达式类型的引用如果有const、volatile限定符不能忽略#include iostream #include vector using namespace std; class Test { public: int num; }; int main() { const Test obj; //带有括号的表达式 decltype(obj.num) a 0; decltype((obj.num)) b a; //加法表达式 int n 0, m 0; decltype(n m) c 0; decltype(n n m) d n; return 0; }obj.num 为类的成员访问表达式符合场景1因此 a 的类型为intobj.num 带有括号符合场景3因此b 的类型为 const int。nm 得到一个右值符合场景1因此c的类型为 intnnm 得到一个左值 n符合场景3因此d的类型为 int5返回值后置在编程中在模板函数中需要知道返回类型#include iostream using namespace std; // R-返回值类型, T-参数1类型, U-参数2类型 template typename R, typename T, typename U R add(T t, U u) { return t u; } int main() { int x 520; double y 13.14; // auto z adddecltype(x y), int, double(x, y); auto z adddecltype(x y)(x, y); // 简化之后的写法 cout z: z endl; return 0; }有时候并不知道函数内部具体返回是什么可能会很复杂。具体格式// 符号 - 后边跟随的是函数返回值的类型 auto func(参数1, 参数2, ...) - decltype(参数表达式)auto 会追踪 decltype() 推导出的类型#include iostream using namespace std; template typename T, typename U // 返回类型后置语法 auto add(T t, U u) - decltype(tu) { return t u; } int main() { int x 520; double y 13.14; // auto z addint, double(x, y); auto z add(x, y); // 简化之后的写法 cout z: z endl; return 0; }6finalC中增加了final关键字来限制某个类不能被继承或者某个虚函数不能被重写如果使用final修饰函数只能修饰虚函数并且要把final关键字放到类或者函数的后面。6.1修饰函数如果使用final修饰函数只能修饰虚函数这样就能阻止子类重写父类的这个函数了class Base { public: virtual void test() { cout Base class...; } }; class Child : public Base { public: void test() final { cout Child class...; } }; class GrandChild : public Child { public: // 语法错误, 不允许重写 void test() { cout GrandChild class...; } };test()是基类中的一个虚函数在子类中重写了这个方法但是不希望孙子类中继续重写这个方法了因此在子类中将test()方法标记为final孙子类中对这个方法就只有使用的份了。6.2修饰类使用final关键字修饰过的类是不允许被继承的也就是说这个类不能有派生类。class Base { public: virtual void test() { cout Base class...; } }; class Child final: public Base { public: void test() { cout Child class...; } }; // error, 语法错误 class GrandChild : public Child { public: };7overrid确保重新写的函数是正确的编译器都会指出这个函数没有重写对应的基类虚函数。class Base { public: virtual void test() { cout Base class...; } }; class Child : public Base { public: void test() override { cout Child class...; } }; class GrandChild : public Child { public: void test() override { cout Child class...; } };8默认模板参数8.1类模板默认参数#include iostream using namespace std; template typename Tint, T t520 class Test { public: void print() { cout current value: t endl; } }; int main() { Test t; t.print(); Testint, 1024 t1; t1.print(); return 0; }8.2函数模板默认参数#include iostream using namespace std; template typename Tint // C98/03不支持这种写法, C11中支持这种写法 void func(T t) { cout current value: t endl; } int main() { func(100); return 0; }当所有模板参数都有默认参数时函数模板的调用如同一个普通函数。但对于类模板而言哪怕所有参数都有默认参数在使用时也必须在模板名后跟随来实例化。9函数指针先定义一个普通函数用来计算两个整数的和int add(int a, int b) { return a b; }平时直接通过函数名调用int result add(2, 3); // result 是 5现在我们用一个指针p指向这个函数p是一个函数指针指向的函数接收两个int返回一个int。int (*p)(int, int) add;部分含义(*p)p是一个指针后面的(int, int)它指向的函数接收两个int参数最前面的int这个函数返回int add让p指向add函数有了p就可以通过它调用addint result p(2, 3); // result 是 5因为p指向add所以此时下面两种调用效果相同add(2, 3); // 通过函数名调用 p(2, 3); // 通过函数指针调用完整代码:#include iostream int add(int a, int b) { return a b; } int main() { int (*p)(int, int) add; std::cout p(2, 3) \n; // 输出 5 return 0; }10定义别名在c中可以通过typedef重新定义以给类型typedef 旧的类型名 新的类型名;使用using定义别名的语法格式是这样的using 新的类型 旧的类型; // 使用举例 using uint_t int;定义函数指针// 使用typedef定义函数指针 typedef int(*func_ptr)(int, double); // 使用using定义函数指针 using func_ptr1 int(*)(int, double);定义模块在定义模块别名中使用typedef非常麻烦在c11中引入了usingtemplate typename T using mymap mapint, T;完整代码如下using namespace std; template typename T using mymap mapint, T; int main(void) { // map的value指定为string类型 mymapstring m; m.insert(make_pair(1, luffy)); m.insert(make_pair(2, ace)); // map的value指定为int类型 mymapint m1; m1.insert(1, 100); m1.insert(2, 200); return 0; }
阅读完成 · 觉得有帮助?