当前位置: 首页 > news >正文

网站后台建设编辑器晋中品牌网站建设建设

网站后台建设编辑器,晋中品牌网站建设建设,wordpress相册展示插件,手表网站哪个最好知乎#x1f4f7; 江池俊#xff1a; 个人主页 #x1f525;个人专栏#xff1a; ✅数据结构冒险记 ✅C那些事儿 #x1f305; 有航道的人#xff0c;再渺小也不会迷途。 文章目录 前言1. C关键字(C98)2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入输出4. 缺… 江池俊 个人主页 个人专栏 ✅数据结构冒险记 ✅C那些事儿 有航道的人再渺小也不会迷途。 文章目录 前言1. C关键字(C98)2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入输出4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载5.1 函数重载引入1、参数【类型】不同构成重载2、参数【个数】不同构成重载3、参数【类型顺序】不同构成重载 5.2 C支持函数重载的原理--名字修饰(name Mangling) 6. 引用6.1 引用概念6.2 引用特性6.3 常引用6.4 使用场景1. 做参数2. 做返回值思考题 6.5 传值、传引用效率比较1、值和引用的作为函数参数的性能比较2、值和引用的作为返回值类型的性能比较 6.6 引用和指针的区别引用和指针的不同点 7. 内联函数PS头文件中定义一个函数如果该头文件在多个位置被包含那么就会出现函数重定义的问题编译器报错那么这个问题该如何解决呢7.1 概念7.2 特性【面试题】宏的优缺点C有哪些技术替代宏 8. auto关键字(C11)8.1 类型别名思考8.2 auto简介8.3 auto的使用细则8.3 auto不能推导的场景 9. 基于范围的for循环(C11)9.1 范围for的语法9.2 范围for的使用条件1. for循环迭代的范围必须是确定的2. 迭代的对象要实现和的操作。 10. 指针空值nullptr(C11)10.1 C98中的指针空值 前言 C是在C的基础之上容纳进去了面向对象编程思想并增加了许多有用的库以及编程范式等。熟悉C语言之后对C学习有一定的帮助本章节主要目标 补充C语言语法的不足以及C是如何对C语言设计不合理的地方进行优化的比如作用域 方面、IO 方面、函数 方面、指针 方面、宏 方面等。为后续 类和对象 学习打基础。 1. C关键字(C98) C总计63个关键字C语言32个关键字 asmdoifreturntrycontinueautodoubleinlineshorttypedefforbooldynamic_castintsignedtypeidpublicbreakelselongsizeoftypenamethrowcaseenummutablestaticunionwchar_tcatchexplicitnamespacestatic_castunsigneddefaultcharexportnewstructusingfriendclassexternoperatorswitchvirtualregisterconstfalseprivatetemplatevoidtrueconst_castfloatprotectedthisvolatilewhiledeletegotoreinterpret_cast 2. 命名空间 在C/C中变量、函数和后面要学到的类都是大量存在的这些变量、函数和类的名称将都存在于全局作用域中可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。 #include stdlib.hint rand 10; // C语言没办法解决类似这样的命名冲突问题所以C提出了namespace来解决 int main() {printf(%d\n, rand);return 0; }// 编译后后报错error C2365: “rand”: 重定义以前的定义是“函数”2.1 命名空间定义 定义命名空间需要使用到 namespace关键字后面跟 命名空间的名字然 后接一对{} 即可{}中即为命名空间的成员。 // lzf是命名空间的名字一般开发中是用项目名字做命名空间名。 // 1. 正常的命名空间定义 namespace lzf {// 命名空间中可以定义变量/函数/类型int rand 10;int Add(int left, int right){return left right;}struct Node{struct Node* next;int val;}; }//2. 命名空间可以嵌套 // test.cpp namespace N1 {int a;int b;int Add(int left, int right){return left right;}namespace N2{int c;int d;int Sub(int left, int right){return left - right;}} }//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。 // ps一个工程中的test.h和上面test.cpp中两个N1会被合并成一个 // test.h namespace N1 {int Mul(int left, int right){return left * right;} }注意一个命名空间就定义了一个新的作用域命名空间中的所有内容都局限于该命名空间中 2.2 命名空间使用 命名空间中成员该如何使用呢比如 namespace lzf {// 命名空间中可以定义变量/函数/类型int a 0;int b 1;int Add(int left, int right){return left right;}struct Node{struct Node* next;int val;}; }int main() {// 编译报错error C2065: “a”: 未声明的标识符printf(%d\n, a);return 0; }命名空间的使用有三种方式 加命名空间名称及作用域限定符命名空间前缀 当定义了一个命名空间后可以通过在实体名称前加上命名空间名称和作用域限定符(::)来显式指定要使用的特定命名空间的实体。 int main() {// 使用命名空间前缀来调用变量 printf(%d\n, lzf::a);return 0; }使用using将命名空间中某个成员引入 可以在特定的作用域内使用using声明来引入命名空间中的一个或多个实体这样就可以直接使用这些实体而无需每次都加上命名空间前缀。 // 使用using声明来引入命名空间中的变量 b using lzf::b; int main() {printf(%d\n, lzf::a);// 现在可以直接调用变量 b无需前缀printf(%d\n, b);return 0; }使用using namespace 命名空间名称 引入 也可以使用using声明来引入整个命名空间这样命名空间中的所有实体都将被引入到当前作用域中。 using namespace lzf; int main() {// 现在可以直接调用命名空间中的所有变量和函数无需前缀printf(%d\n, a);printf(%d\n, b);printf(%d\n, Add(10, 20));return 0; }3. C输入输出 新生婴儿会以自己独特的方式向这个崭新的世界打招呼C刚出来后也算是一个新事物 那C是否也应该向这个美好的世界来声问候呢我们来看下C是如何来实现问候的。 #include iostream // std是C标准库的命名空间名C将标准库的定义实现都放到这个命名空间中 using namespace std; int main() {cout Hello world!!! endl;return 0; }说明 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时必须包含 iostream 头文件 以及按命名空间使用方法使用std。cout和cin是全局的流对象endl是特殊的C符号表示换行输出他们都包含在包含 iostream 头文件中。 是流插入运算符 是流提取运算符。使用C输入输出更方便不需要像 printf/scanf 输入输出时那样需要手动控制格式。C的输入输出可以自动识别变量类型。实际上 cout 和 cin 分别是 ostream 和 istream 类型的对象和也涉及运算符重载等知识我们这里只是简单学习他们的使用。 注意早期标准库将所有功能在全局域中实现声明在 .h 后缀的头文件中使用时只需包含对应头文件即可后来将其实现在std命名空间下为了和C头文件区分也为了正确使用命名空间规定C头文件不带.h旧编译器(vc 6.0)中还支持iostream.h格式后续编译器已不支持因此推荐使 iostream std 的方式。 #include iostream using namespace std; int main() {int a;double b;char c;// 无需使用格式化输入控制可以自动识别变量的类型cin a;cin b c;cout a endl;cout b c endl;return 0; } // ps关于cout和cin还有很多更复杂的用法比如控制浮点数输出精度控制整形输出进制格式等等。 // 因为C兼容C语言的用法这些又用得不是很多我们这里就不展开学习了。后续如果有需要可以自行配合文档学习。std命名空间的使用惯例 std是C标准库的命名空间如何展开std使用更合理呢 在日常练习中建议直接using namespace std即可这样就很方便。using namespace std展开标准库就全部暴露出来了如果我们定义跟库重名的类型/对象/函数就存在冲突问题。该问题在日常练习中很少出现但是项目开发中代码较多、规模大就很容易出现。所以建议在项目开发中使用像std::cout这样使用时指定命名空间 using std::cout展开常用的库对象/类型等方式。 4. 缺省参数 4.1 缺省参数概念 缺省参数是 声明或定义函数 时为函数的 参数指定一个缺省值。在调用该函数时如果没有指定实参则采用该形参的缺省值否则使用指定的实参。 void Func(int a 0) {cout a endl; } int main() {Func(); // 没有传参时使用参数的默认值Func(10); // 传参时使用指定的实参return 0; }4.2 缺省参数分类 全缺省参数 void Func(int a 10, int b 20, int c 30) {cout a a endl;cout b b endl;cout c c endl; }半缺省参数 void Func(int a, int b 10, int c 20) {cout a a endl;cout b b endl;cout c c endl; }注意 半缺省参数必须从右往左依次来给出不能间隔着给缺省参数只能用于函数声明中不能用于函数定义中。 缺省参数只能用于函数声明中不能用于函数定义中主要是因为在C中函数的定义和声明在编译过程中有不同的作用。 函数声明Function Declaration函数声明主要用于告诉编译器函数的名称、参数列表以及参数类型。这允许编译器在编译阶段检查函数调用的正确性即使函数的具体实现即函数定义尚未出现。 函数定义Function Definition函数定义提供了函数的实际实现即函数体中的代码。这是编译器生成函数实际执行代码所必需的。 当我们在函数声明中使用缺省参数时我们实际上是在告诉编译器如果在调用函数时没有提供某个参数的值那么应该使用哪个默认值。这种信息在编译阶段就需要知道以便编译器可以进行类型检查和生成正确的代码。 如果在函数定义中使用缺省参数那么这将导致以下问题 类型检查问题如果只在函数定义中提供缺省参数那么在函数声明中就没有这些参数的信息。这意味着在编译阶段编译器无法检查函数调用中是否提供了正确的参数数量和类型。 多文件编译问题在C的多文件项目中函数定义通常在一个文件中而函数声明则在头文件中。如果缺省参数只出现在定义中那么在其他文件中包含该头文件时编译器将无法知道这些缺省参数的存在导致编译错误。 代码可读性和维护性将缺省参数分散在函数定义和声明中可能会降低代码的可读性和可维护性。理想情况下函数的所有相关信息包括参数和它们的缺省值应该集中在一个地方即函数声明中。 因此为了保持类型检查的一致性、支持多文件编译以及维护代码的可读性和可维护性C规定缺省参数只能出现在函数声明中。 缺省参数的值在编译时确定而不是在运行时确定。缺省值必须是常量或者全局变量C语言不支持编译器不支持 5. 函数重载 自然语言中一个词可以有多重含义人们可以通过上下文来判断该词真实的含义即该词被重载了。 比如以前有一个笑话国有两个体育项目大家根本不用看也不用担心。一个是乒乓球一个是男足。前者是“谁也赢不了”后者是“谁也赢不了” 5.1 函数重载引入 函数重载是函数的一种特殊情况C允许在同一作用域中声明几个功能类似的同名函数这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同常用来处理实现功能类似数据类型不同的问题。 1、参数【类型】不同构成重载 // 1、参数类型不同 int Add(int left, int right) {cout int Add(int left, int right) endl;return left right; } double Add(double left, double right) {cout double Add(double left, double right) endl;return left right; }2、参数【个数】不同构成重载 // 2、参数个数不同 void f() {cout f() endl; } void f(int a) {cout f(int a) endl; }3、参数【类型顺序】不同构成重载 // 3、参数类型顺序不同 void f(int a, char b) {cout f(int a,char b) endl; } void f(char b, int a) {cout f(char b, int a) endl; }5.2 C支持函数重载的原理–名字修饰(name Mangling) 为什么C支持函数重载而C语言不支持函数重载呢 在C/C中一个程序要运行起来需要经历以下几个阶段预处理、编译、汇编、链接。欲知详情请跳转程序环境预处理 实际项目通常是由多个头文件和多个源文件构成而通过C语言阶段学习的编译链接我们可以知道【当前a.cpp中调用了b.cpp中定义的Add函数时】编译后链接前a.o的目标文件中没有Add的函数地址因为Add是在b.cpp中定义的所以Add的地址在b.o中。那么怎么办呢所以链接阶段就是专门处理这种问题链接器看到a.o调用Add但是没有Add的地址就会到b.o的符号表中找Add的地址然后链接到一起。那么链接时面对Add函数链接接器会使用哪个名字去找呢这里每个编译器都有自己的函数名修饰规则。由于Windows下vs的修饰规则过于复杂而Linux下g的修饰规则简单易懂下面我们使用了g演示了这个修饰后的名字。通过下面我们可以看出gcc的函数修饰后名字不变。而g的函数修饰后变成【_Z函数长度函数名类型首字母】。 采用C语言编译器编译后结果 结论在linux下采用gcc编译完成后函数名字的修饰没有发生改变。采用C编译器编译后结果 结论在linux下采用g编译完成后函数名字的修饰发生改变编译器将函数参数类型信息添加到修改后的名字中。 PS g 是 GNU C 编译器的命令行工具用于编译 C 代码。-o 是 g 的一个选项用于指定输出文件的名称。当你使用 g 编译一个或多个源文件时默认情况下它会生成一个名为 a.out 的可执行文件在 UNIX 和 Linux 系统中。通过使用 -o 选项你可以自定义这个输出文件的名称。 objdump 是一个用于显示二进制文件信息的命令行工具它主要用于显示可执行文件、目标文件、共享库和内核映像的信息。当你使用 -s 选项时objdump 会显示指定 section段的 原始字节内容即在Linux下查看反汇编。 Windows下名字修饰规则 在Windows环境下 VS编译器中C的函数名修饰规则为 【? 函数名 YA 返回值 参数1 参数2 Z】int类型对应的是字母Hvoid类型对应的是字母Xdouble类型对应的是字母Nfloat类型对应的是字母M。其中YA中的A表示函数调用类型为“_cdecl”还有其他的函数调用类型如“__stdcall”、“__fastcall”等 函数签名修饰后名称int func(int)?funcYAHHZfloat func(float)?funcYAMMZint C::func(int)?funcCAAEHHZint C::C2::func(int)?funcC2CAAEHHZ 对比Linux会发现windows下vs编译器对函数名字修饰规则相对复杂难懂但道理都是类似的。 通过这里就理解了C语言没办法支持重载因为同名函数没办法区分。而C是通过函数修饰规则来区分只要参数不同修饰出来的名字就不一样就支持了重载。如果两个函数函数名和参数是一样的返回值不同是不构成重载的因为调用时编译器没办法区分。 6. 引用 6.1 引用概念 引用 不是新定义一个变量而 是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量 共用同一块内存空间。 比如李逵在家称为 “铁牛”江湖上人称 “黑旋风”。 类型 引用变量名(对象名) 引用实体 void TestRef() {int a 10;int ra a;//定义引用类型printf(%p\n, a);printf(%p\n, ra); }注意引用类型 必须和引用 实体 是 同种类型的 6.2 引用特性 引用在定义时必须初始化一个变量可以有多个引用引用一旦引用一个实体再不能引用其他实体故C的引用对指针使用比较复杂的场景进行一些替换让代码更简单易懂但是不能完全替代指针 void TestRef() {int a 10;// int ra; // 该条语句编译时会出错int ra a;int rra a;printf(%p %p %p\n, a, ra, rra); } 6.3 常引用 在C中“常引用”const reference是一种特殊的引用类型它引用的对象不能被修改。常引用在多个场合都非常有用特别是在函数参数传递和函数返回值中。 void TestConstRef() {const int a 10;// int ra a; // 该语句编译时会出错a为常量const int ra a;// int b 10; // 该语句编译时会出错b为常量const int b 10;double d 12.34;// int rd d; // 该语句编译时会出错类型不同const int rd d; // 隐式类型转换会产生临时变量临时变量具有常性 }注意隐式类型转换时会产生临时变量临时变量具有常性 6.4 使用场景 1. 做参数 通过引用传递参数可以避免复制大型对象提高程序的效率。此外通过引用传递参数还可以使函数修改调用者提供的变量的值。当不希望函数内部修改参数的值时可以使用常引用。 void Swap(int left, int right) {int temp left;left right;right temp; }2. 做返回值 函数可以返回引用这样可以使函数返回一个可修改的对象而不是返回对象的副本拷贝。这在重载赋值运算符和数组下标运算符等场景下非常有用。同样当想返回一个对象但又不希望这个对象被修改时可以使用常引用。 int Count() {static int n 0;n;// ...return n; }引用提供了一种更加安全和简洁的方式来访问和操作数据相对于指针来说它不需要进行额外的空指针检查和解引用操作。 思考题 下面代码输出什么结果为什么 int Add(int a, int b) {int c a b;return c; } int main() {int ret Add(1, 2);Add(3, 4);cout Add(1, 2) is : ret endl;return 0; }这段代码的输出结果是未定义的。原因是函数返回的是局部变量的引用在函数结束后该变量的空间被释放我们无法去使用这块空间所以最后输出的结果是未定义的。 未定义行为是C中的一个概念它意味着程序的行为不是由语言标准规定的因此不同的编译器或运行环境可能会产生不同的结果或者程序可能会崩溃。 为了避免这种情况我们应该避免返回局部变量的引用。如果想在函数内部计算值并在函数外部使用可以返回该值的副本即值本身而不是引用。如果需要返回引用那么该引用必须引用一个在整个函数外部可见且生命周期足够长的对象。 注意如果函数返回时出了函数作用域如果返回对象还在(还没还给系统)则可以使用引用返回如果已经还给系统了则必须使用传值返回。 6.5 传值、传引用效率比较 以值作为参数或者返回值类型在传参和返回期间函数不会直接传递实参或者将变量本身直接返回而是传递实参或者返回变量的一份临时的拷贝因此用值作为参数或者返回值类型效率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低。 1、值和引用的作为函数参数的性能比较 #include time.h struct A { int a[10000]; };void TestFunc1(A a) {} void TestFunc2(A a) {}void TestRefAndValue() {A a;// 以值作为函数参数size_t begin1 clock();for (size_t i 0; i 10000; i)TestFunc1(a);size_t end1 clock();// 以引用作为函数参数size_t begin2 clock();for (size_t i 0; i 10000; i)TestFunc2(a);size_t end2 clock();// 分别计算两个函数运行结束后的时间cout TestFunc1(A)-time: end1 - begin1 endl;cout TestFunc2(A)-time: end2 - begin2 endl; }int main() {TestRefAndValue();return 0; }2、值和引用的作为返回值类型的性能比较 #include time.h struct A { int a[10000]; }; A a; // 值返回 A TestFunc1() { return a; } // 引用返回 A TestFunc2() { return a; }void TestReturnByRefOrValue() {// 以值作为函数的返回值类型size_t begin1 clock();for (size_t i 0; i 100000; i)TestFunc1();size_t end1 clock();// 以引用作为函数的返回值类型size_t begin2 clock();for (size_t i 0; i 100000; i)TestFunc2();size_t end2 clock();// 计算两个函数运算完成之后的时间cout TestFunc1 time: end1 - begin1 endl;cout TestFunc2 time: end2 - begin2 endl; }int main() {TestReturnByRefOrValue();return 0; }通过上述代码的比较发现传值和引用在作为传参以及返回值类型上效率相差很大。 6.6 引用和指针的区别 在语法概念上引用就是一个别名没有独立空间和其引用实体 共用同一块空间。 int main() {int a 10;int ra a;cout a a endl;cout ra ra endl;return 0; }在底层实现上实际是有空间的因为 引用是按照指针方式来实现 的。 int main() {int a 10;int ra a;ra 20;int* pa a;*pa 20;return 0; }我们来看下引用和指针的汇编代码对比 引用和指针的不同点 引用概念上定义一个变量的别名指针存储一个变量地址。引用在定义时必须初始化指针没有要求引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体没有NULL引用但有NULL指针在sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节)引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小有多级指针但是没有多级引用访问实体方式不同指针需要显式解引用引用编译器自己处理引用比指针使用起来相对更安全 7. 内联函数 PS头文件中定义一个函数如果该头文件在多个位置被包含那么就会出现函数重定义的问题编译器报错那么这个问题该如何解决呢 有三种方法 声明和定义分离防止链接冲突 只在头文件中进行函数声明而不是定义函数。然后在源文件中实现这个函数。这样即使头文件被多次包含也不会出现重定义的问题。 static静态函数链接属性只在当前文件可见 在C中可以将函数定义为静态的。这意味着函数只在定义它的文件内部可见底层就是此函数不会进符号表不能声明和定义分离。这样即使函数在多个文件中被包含也不会出现重定义的问题。 inline内联函数不能声明和定义分离 原理与static相似函数不进符号表 7.1 概念 以inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销内联函数提升程序运行的效率。 如果在上述函数前增加inline关键字将其改成内联函数在编译期间编译器会用函数体替换函数的调用。 查看方式 在release模式下查看编译器生成的汇编代码中是否存在call Add在debug模式下需要对编译器进行设置否则不会展开(因为debug模式下编译器默认不会对代码进行优化以下给出vs2022的设置方式) 7.2 特性 inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用缺陷可能会使目标文件变大优势少了调用开销提高程序运行效率。inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建议将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰否则编译器会忽略inline特性。下图为 《Cprime》第五版关于inline的建议 inline不建议声明和定义分离分离会导致链接错误。因为inline被展开就没有函数地址了链接就会找不到。 // F.h #include iostream using namespace std;inline void f(int i); // 无函数地址// F.cpp #include F.h void f(int i) {cout i endl; }// main.cpp #include F.h int main() {f(10);return 0; }/* 链接错误main.obj : error LNK2019: 无法解析的外部符号 void __cdecl f(int) (?fYAXHZ)该符号在函数 _main 中被引用 */【面试题】 宏的优缺点 优点 增强代码的复用性。提高性能。 缺点 不方便调试宏。因为预编译阶段进行了替换导致代码可读性差可维护性差容易误用。没有类型安全的检查 。 C有哪些技术替代宏 常量定义 换用 const enum短小函数定义 换用内联函数\ 8. auto关键字(C11) 8.1 类型别名思考 随着程序越来越复杂程序中用到的类型也越来越复杂经常体现在 类型难于拼写含义不明确导致容易出错 #include string #include mapint main() {std::mapstd::string, std::string m{ { apple, 苹果 }, { orange, 橙子 },{pear,梨} };std::mapstd::string, std::string::iterator it m.begin();while (it ! m.end()){//....}return 0; }std::mapstd::string, std::string::iterator 是一个类型但是该类型太长了特别容易写错。聪明的同学可能已经想到可以通过 typedef 给类型取别名比如 #include string #include maptypedef std::mapstd::string, std::string Map; int main() {Map m{ { apple, 苹果 },{ orange, 橙子 }, {pear,梨} };Map::iterator it m.begin();while (it ! m.end()){//....}return 0; }使用 typedef 给类型取别名确实可以简化代码但是 typedef 又会遇到新的难题 typedef char* pstring; int main() {const pstring p1; // 编译成功还是失败const pstring* p2; // 编译成功还是失败return 0; }这段代码中const pstring p1; 和 const pstring* p2; 的编译都会失败。 在C中const关键字用于声明常量表示该变量的值不能被改变。然而const关键字也可以用于修饰指针。当const关键字放在星号()前面时它表示指针本身是常量即不能改变指针指向的位置当const关键字放在星号()后面时它表示指针指向的内容是常量即不能通过该指针改变所指向的内容。 在这段代码中const pstring p1;试图定义一个常量指针p1但是没有初始化这是不允许的。同样const pstring* p2;试图定义一个指向常量字符指针的指针p2但是也没有初始化。因此这两行代码都会引发编译错误。 在编程时常常需要把表达式的值赋值给变量这就要求在声明变量的时候清楚地知道表达式的类型。然而有时候要做到这点并非那么容易因此C11给auto赋予了新的含义。 8.2 auto简介 C11中标准委员会赋予了auto全新的含义即auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。 int TestAuto() {return 10; }int main() {int a 10;auto b a; // 类型是 intauto c a; // 类型是 char//int(*pf)() TestAuto;auto pf TestAuto; // 类型是 int(*)()auto d TestAuto(); // 类型是 int// typeid().name 打印变量类型cout typeid(b).name() endl; cout typeid(c).name() endl;cout typeid(pf).name() endl;cout typeid(d).name() endl;//auto e; 无法通过编译使用auto定义变量时必须对其进行初始化return 0; } 【注意】 使用 auto 定义变量时必须对其进行初始化在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明而是一个类型声明时的“占位符”编译器在编译期会将auto替换为变量实际的类型。 8.3 auto的使用细则 auto与指针和引用结合起来使用 用auto声明指针类型时用auto和auto*没有任何区别但用auto声明引用类型时则必须加 在同一行定义多个变量 当在同一行声明多个变量时这些变量必须是相同的类型否则编译器将会报错因为编译器实际只对第一个类型进行推导然后用推导出来的类型定义其他变量。 void TestAuto() {auto a 1, b 2;auto c 3, d 4.0; // 该行代码会编译失败因为c和d的初始化表达式类型不同 }8.3 auto不能推导的场景 auto不能作为函数的参数 // 此处代码编译失败auto不能作为形参类型因为编译器无法对a的实际类型进行推导 void TestAuto(auto a) {}auto不能直接用来声明数组 3. 为了避免与C98中的auto发生混淆C11只保留了auto作为类型指示符的用法 4. auto在实际中最常见的优势用法就是跟以后会讲到的C11提供的 新式for循环还有 lambda表达式等进行配合使用。 9. 基于范围的for循环(C11) 9.1 范围for的语法 在C98中如果要遍历一个数组可以按照以下方式进行 void TestFor() {int array[] { 1, 2, 3, 4, 5 };for (int i 0; i sizeof(array) / sizeof(array[0]); i)array[i] * 2;for (int* p array; p array sizeof(array) / sizeof(array[0]); p)cout *p endl; }int main() {TestFor();return 0; } 对于一个有范围的集合而言由程序员来说明循环的范围是多余的有时候还会容易犯错误。因此C11中引入了基于范围的for循环。for循环后的括号由冒号“ ”分为两部分第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围。 void TestFor() {int array[] { 1, 2, 3, 4, 5 };for (auto e : array)e * 2;for (auto e : array)cout e endl; }int main() {TestFor();return 0; }注意与普通循环类似可以用 continue 来结束本次循环也可以用 break 来跳出整个循环。 9.2 范围for的使用条件 1. for循环迭代的范围必须是确定的 对于数组而言就是数组中第一个元素和最后一个元素的范围对于类而言应该提供begin和end的方法begin和end就是for循环迭代的范围。 注意以下代码就有问题因为for的范围不确定 // 传入的参数是数组的首地址无法获取数组的大小 void TestFor(int array[]) {for(auto e : array)cout e endl; }2. 迭代的对象要实现和的操作。 10. 指针空值nullptr(C11) 10.1 C98中的指针空值 在良好的C/C编程习惯中声明一个变量时最好给该变量一个合适的初始值否则可能会出现不可预料的错误比如未初始化的指针。如果一个指针没有合法的指向我们基本都是按照如下方式对其进行初始化 void TestPtr() {int* p1 NULL;int* p2 0;// …… }NULL 实际是一个宏在传统的 C头文件(stddef.h)中可以看到如下代码 #ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif #endif可以看到NULL 可能被定义为字面常量0或者被定义为无类型指针(void*)的常量。不论采取何种定义在使用空值的指针时都不可避免的会遇到一些麻烦比如 void f(int) {cout f(int) endl; }void f(int*) {cout f(int*) endl; }int main() {f(0);f(NULL);f((int*)NULL);return 0; }程序本意是想通过f(NULL)调用指针版本的f(int*)函数但是由于NULL被定义成0因此与程序的初衷相悖。 在C98中字面常量0既可以是一个整形数字也可以是无类型的指针(void*)常量但是编译器默认情况下将其看成是一个整形常量如果要将其按照指针方式来使用必须对其进行强转(void *)0。 注意 在使用 nullptr 表示指针空值时不需要包含头文件因为 nullptr 是C11作为新关键字引入的。在C11中sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。为了提高代码的健壮性在后续表示指针空值时建议最好使用 nullptr。
http://www.zqtcl.cn/news/545692/

相关文章:

  • 建设银行网站最近打不开吗wordpress c
  • 网站icp备案费用浅谈做网站的好处
  • 制作网站需要懂哪些在线设计平台的市场调研
  • 接计设做的网站河南网站建设华企祥云
  • 网站系统维护一般要多久企业网站推广工具
  • 如何诊断网站seo做个网站商场需要多少
  • 腾讯云做视频网站吗创业商机网加工项目
  • 网站建设论文文献郑州seo外包费用
  • 网站优化西安如何免费推广网站
  • 固原市建设局网站外贸网站建设方法
  • 做违规网站主页制作语言缩写
  • 汝南县网站建设怎么注册公司钉钉账号
  • 网站建设酷隆信通网站开发中心
  • 保定网站建设方案报价怎么做网站_
  • 做网站功能的框架结构图做网站用python好吗
  • 襄樊市网站建设模版网站建设企业
  • 网站换服务器php大流量网站开发规范
  • 网站备案主体域名平面设计线下培训班多少钱
  • 优秀网站专题wordpress 外部调用插件
  • 域名服务网站建设科技公司做棋子网站怎么提高浏览量
  • 用易语言做攻击网站软件下载彩页设计多少钱
  • 个人网站可以做淘宝推广手机版怎么用百度快照
  • 制作网站的公司叫什么外包软件
  • 廊坊企业建站模板邱县手机网站建设
  • 辽宁响应式网站费用建设银行官网app
  • 河北黄骅市网站建设网站外链的优化方法
  • 青岛城阳网站制作网站建设详细步骤
  • 先做网站再付款 怎么回答设计方案步骤
  • 汕头建站模板济南网站建设富库网络
  • 创业网站建设方案项目书手机app设计软件