关于网站的制作,西安信息网站建设,网络推广方案策划书,上海市建设人才网站前言 一、inline
概念 以inline修饰的函数叫做内联函数#xff0c;编译时C编译器会在调用内联函数的地方展开#xff0c;没有函数调用建立栈帧的开销#xff0c;内联函数提升程序运行的效率。 如果在上述函数前增加inline关键字将其改成内联函数#xff0c;在编译期间编译…前言 一、inline
概念 以inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销内联函数提升程序运行的效率。 如果在上述函数前增加inline关键字将其改成内联函数在编译期间编译器会用函数体替换函数的 调用。 查看方式 在release模式下查看编译器生成的汇编代码中是否存在call Add在debug模式下需要对编译器进行设置否则不会展开(因为debug模式下编译器默认不会对代码进行优化以下给出vs2022的设置方式) 特性 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 中被引用 分析由于函数f(int) 被声明为 inline类型在编译阶段会对它进行展开而且不会被加入符号表但是由于 文件main.cpp 包含的 F.h头文件 中只有声明没有定义因此展开的只有声明所以在链接时会出错。 解决方法内联函数声明和定义都写到 .h文件中。 使用inline的原由 使用inline是为了替代C语言中的宏函数。 宏的优点 短小代码直接替换不需要进行函数调用速度更快提高性能没有类型限制提高代码复用性 宏的缺点 无法进行调试没有类型安全的检查会大大增加源代码长度。导致代码可读性差可维护性差容易误用。 宏定义的内容会在预处理阶段进行替换因此在调试时是找不到的。 因此我们在日常练习的时候可以减少对宏的使用 当然宏也有其不可替代的功能传递变量类型 eg计算结构体成员偏移量的宏offsetof #define MY_offsetof(s, m) (size_t)(((s*)0)-m) #define MY_offsetof(s, m) (size_t)(((s*)0)-m) class Person
{
public:char name[22];int age;
};void Test03()
{cout offsetof(Person, Person::age) endl;cout MY_offsetof(Person, Person::age) endl;
}结果 二、autoc11
引入
随着程序越来越复杂程序中用到的类型也越来越复杂主要表现在两个方面
类型难以拼写含义不明确导致出错。
示例这里以实际开发情况为例
#includemap
#includestringvoid Test01()
{std::mapstd::string, std::string m { {宁姚, 剑气长城} , {裴钱,莲藕福地} };std::mapstd::string, std::string::iterator it m.begin();while (it ! m.end()){std::cout it-first it-second std::endl;it;}
} std::mapstd::string, std::string::iterator这一长串字符就是 it 的类型名可见是比较复杂的 这里我们有一种比较简单的方法是使用 typedef typedef std::mapstd::string, std::string::iterator MAP; 不过这样仍然需要再写一遍类型名还是会有出错的情况 而且typedef在设计时是有些“小问题的”使用起来会让人难以理解比如 void Test02()
{typedef int* T;const T val1;
}
// 在编程时常常需要把表达式的值赋值给变量这就要求在声明变量的时候清楚地知道表达式的类型。
// 然而有时候要做到这点并非那么容易因此C11给auto赋予了新的含义使用 在早期C/C中auto的含义是使用auto修饰的变量是具有自动存储器的局部变量但遗憾的是一直没有人去使用它大家可思考下为什么 C11中标准委员会赋予了auto全新的含义即auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。 int TestAuto()
{
return 10;
}
int main()
{
int a 10;
auto b a;
auto c a;
auto d TestAuto();
cout typeid(b).name() endl;
cout typeid(c).name() endl;
cout typeid(d).name() endl;
//auto e; 无法通过编译使用auto定义变量时必须对其进行初始化
return 0;
}auto类型的变量必须初始化因为编译器在编译阶段需要通过初始化数据来推导变量的类型 因此auto并不是类型而是类型占位符在编译阶段会被替换为变量的实际类型。
注意事项
auto与指针和引用联用
void TestAuto()
{auto val 10;auto p1 val; // 设置指针时加不加 *号都一样加上后可以当做提醒auto* p2 val;auto b val; // 设置引用时必须写明 符号cout typeid(val).name() endl;cout typeid(p1).name() endl;cout typeid(p2).name() endl;cout typeid(b).name() endl;
} 使用auto在同一行声明多个变量时每个变量必须是同一类型 因为编译器实际上只对第一个变量进行推导然后用推导出来的类型初始化其余变量。
void TestAuto()
{auto a 1, b 2; auto c 3, d 4.0; // 该行代码会编译失败因为c和d的初始化表达式类型不同
}auto不能作为函数的参数
// 此处代码编译失败auto不能作为形参类型因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}auto不能声明数组
void TestAuto()
{int a[] {1,2,3};auto b[] {456};
}为了避免与C98中的auto发生混淆C11只保留了auto作为类型指示符的用法auto在实际中最常见的优势用法就是跟以后会讲到的C11提供的新式for循环还有lambda表达式等进行配合使用。 三、范围for循环c11
语法
在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;
}对于一个有范围的集合而言由程序员来说明循环的范围是多余的有时候还会容易犯错误。 因此C11中引入了基于范围的for循环。 for循环后的括号由冒号“ ”分为两部分第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围。 void TestFor()
{
int array[] { 1, 2, 3, 4, 5 };
for(auto e : array) // 朋友们可以思考为何此处需要加 引用e * 2;
for(auto e : array)cout e ;
return 0;
}注意与不同for循环类似可以使用continue结束本次循环也可以使用break跳出整个循环。
使用条件
for循环迭代的范围必须是确定的对于数组而言就是数组中第一个元素和最后一个元素的范围 对于类而言应该提供begin和end的方法begin和end就是for循环迭代的范围。 注意以下代码就有问题因为for的范围不确定
void TestFor(int array[]) // 数组名作为参数传递时只传首地址。
{for(auto e : array) cout e endl;
}迭代的对象要实现和的操作。 四、指针空值nullptrc11
引入 在良好的C/C编程习惯中声明一个变量时最好给该变量一个合适的初始值否则可能会出现不可预料的错误比如未初始化的指针。 如果一个指针没有合法的指向我们基本都是按照如下方式对其进行初始化 void TestPtr()
{
int* p1 NULL;
int* p2 0;
// ……
}NULL实际上是一个宏在头文件stddef.h中有如此定义 可以看到NULL可能被定义为字面常量0或者被定义为无类型指针(void*)的常量。 不论采取何种定义在使用空值的指针时都不可避免的会遇到一些麻烦比如 void f(int)
{coutf(int)endl;
}void f(int*)
{coutf(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。
void f(int)
{cout f(int) endl;
}void f(int*)
{cout f(int*) endl;
}void Test04()
{f(NULL);f(nullptr);cout sizeof(int) sizeof(int) endl;cout sizeof(NULL) sizeof(NULL) endl;cout sizeof(void*) sizeof(void*) endl;cout sizeof(nullptr) sizeof(nullptr) endl;
} 补充此处为何不就NULL的宏定义进行修改而从新引入一个新的关键字nullptr 因为语言是不断发展的在发展过程中会有数以万计的程序员写下以亿为单位的代码说不得就有程序员就是想要NULL代表0 因此语言在发展过程中即便之前的内容有问题也不会修改原本的内容而是再创造出一种新的语法。