汉中网站建设公司做旅游网站的
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:57
当前位置: 首页 > news >正文
汉中网站建设公司,做旅游网站的,阿联酋网站后缀,西餐网页设计素材 #x1f4dd;个人主页#xff1a;Sherry的成长之路 #x1f3e0;学习社区#xff1a;Sherry的成长之路#xff08;个人社区#xff09; #x1f4d6;专栏链接#xff1a;C初阶 #x1f3af;长路漫漫浩浩#xff0c;万事皆有期待 上一篇博客#xff1a;【C初阶】… 个人主页Sherry的成长之路 学习社区Sherry的成长之路个人社区 专栏链接C初阶 长路漫漫浩浩万事皆有期待 上一篇博客【C初阶】C入门(一):命名空间C的输入输出缺省参数函数重载 文章目录 1.引用1.1引用的概念1.2引用的特性1.2.1引用在定义时必须初始化1.2.2一个变量可以有多个引用1.2.3引用一旦引用了一个实体就不能再引用其他实体 1.3引用的使用场景1.3.1引用做参数(输出型参数)1.3.2解决二级指针难懂的问题 1.3.3引用做返回值 1.4常引用1.5引用和指针的区别 2.内联函数2.1内联函数的概念2.2 特性 3.auto关键字(C11)3.1auto简介3.2 auto的使用细则3.2.1 auto与指针和引用结合起来使用3.2.2在同一行定义多个变量 3.3 auto不能推导的场景3.3.1auto不能作为函数的参数3.3.2auto不能直接用来声明数组 4.基于范围的for循环(C11)4.1范围for的使用条件4.1.1for循环迭代的范围必须是确定的4.1.2迭代的对象要实现和操作 5.指针空值nullptr5.1 C98中的指针空值5.2 C11中的指针空值 6.总结 1.引用
1.1引用的概念
引用不是定义一个变量而是已存在的变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。
其使用的基本形式为类型 引用变量名(对象名) 引用实体。
#include iostream
using namespace std;
int main()
{int a 10;int b a;//给变量a去了一个别名叫 bcout a a endl;//a打印结果为10cout b b endl;//b打印结果也是10b 20;//改变b也就是改变了acout a a endl;//a打印结果为20cout b b endl;//b打印结果也是为20return 0;
}注引用类型必须和引用实体是同种类型。
1.2引用的特性
1.2.1引用在定义时必须初始化
正确示例
int a 10;
int b a;//引用在定义时必须初始化错误示例
int c 10;
int d;//定义时未初始化
d c;1.2.2一个变量可以有多个引用
例如
int a 10;
int b a;
int c a;
int d a;此时b、c、d都是变量a的引用。
1.2.3引用一旦引用了一个实体就不能再引用其他实体
例如
int a 10;
int b a;此时b已经是a的引用了b不能再引用其他实体。如果写下以下代码想让b引用另一个变量c
int a 10;
int b a;
int c 20;
b c;//错误想法让b转而引用c但该代码的意思是将b引用的实体赋值为c也就是将变量a的内容改成了20。
1.3引用的使用场景
1.3.1引用做参数(输出型参数)
形参的改变影响实参的参数叫做输出型参数对于输出型参数使用引用十分方便。 C语言中的交换函数学习C语言的时候经常用交换函数来说明传值和传址的区别。现在我们学习了引用可以不用指针作为形参了
//交换函数
void Swap(int a, int b)
{int tmp a;a b;b tmp;
}因为在这里a和b是传入实参的引用我们将a和b的值交换就相当于将传入的两个实参交换了。
1.3.2解决二级指针难懂的问题
在单链表的C语言实现的这篇博客里由于是没有头结点的链表所以修改时需要二级指针刚开始学习的时候可能比较难理解。但是学了引用就可以解决这个问题
结构定义
typedef struct SListNode
{int data;struct SListNode* next;
}SLTNode;原代码
void SListPushFront(SLTNode** pphead, SLTDateType x)
{SLTNode* newnode BuyListNode(x);newnode-next *pphead; pphead newnode;
}// 调用
SLTNode pilst NULL;
SListPushFront(plist);修改后的二级指针被替换成了引用:
void SListPushFront(SLTNode* pphead, SLTDateType x) // 修改
{SLTNode* newnode BuyListNode(x);newnode-next *pphead; pphead newnode;
}// 调用
SLTNode pilst NULL;
SListPushFront(plist); // 修改这里的意思是给一级指针取了一个别名传过来的是plist而plist 是一个一级指针所以会出现 * 。相当于 pphead 是 plist 的别名这里修改 pphead 也就可以对 plist 完成修改。
也可以这么写
typedef struct SListNode
{int data;struct SListNode* next;
}SLTNode, PSLTNode;意思就是将 struct SListNode 类型重命名为 PSLTNode
void SListPushFront(PSLTNode pphead, SLTDateType x) // 改
{PSLTNode newnode BuyListNode(x);newnode-next pphead; pphead newnode;
}// 调用
PSLTNode plist NULL;
SListPushFront(plist);在 typedef 之后PSLTNode 就是结构体指针所以传参过去只需要在形参那边用引用接收随后进行操作就可以达成目的。 总结:引用做参数优点 1.作输出型参数 2.提高效率(大对象/深拷贝对象–之后学习) 1.3.3引用做返回值
引用也可以做返回值但要注意一些问题。
int Count()
{int n 0;n;return n;
}int main()
{int ret Count();cout ret endl;return 0;
}这里看似很简单就是把Count函数计算结束的结果返回但是这里包含了 传值返回 。
若从栈帧角度看会先创建 main 函数的栈帧里面就会有 call指令开始调用Count 函数。 Count 函数也会形成栈帧而栈帧中也有空间用来接受参数里面的 n 则用来计算结果并返回。
对于传值返回返回的并不是 n 而是返回的是 n 的拷贝。而这其中会有一个临时变量返回的是临时变量 反向证明如果返回的是 n 的话由于Count 的函数栈帧已经销毁了这里打印的ret的值是不确定的。因为空间已经归还给操作系统了这时都是非法访问所以必定是n拷贝后的数据被返回。
1.如果Count 函数结束栈帧销毁没有清理栈帧那么ret的结果侥幸正确 2.如果Count 函数结束栈帧销毁清理栈帧那么ret的结果是随机值 但是临时变量在哪 如果 n 比较小(4⁄8 byte)一般是寄存器充当临时变量例如eax 如果 n 比较大临时变量放在调用 add 函数的栈帧中 最后将临时变量中的值赋值给ret
不论这个函数结束返回的那个值会不会被销毁都会创建临时变量返回例如
int Count()
{static int n 0;n;return n;
}int main()
{int ret Count();cout ret endl;return 0;
}对于该函数编译器仍然是创建临时变量返回因为编译器不会对其进行特殊处理仍然是放到 eax 寄存器中返回的。
但这个临时变量创建的有点多余明明这块空间一直存在却依然创建临时变量返回
那如果改成引用返回会修改这个缺陷吗
int Count()
{int n 0;n;return n;
}int main()
{int ret Count();cout ret endl;return 0;
}引用返回就是不生成临时变量直接返回 n 的引用。而这里产生的问题就是 非法访问 。 造成的问题 1.存在非法访问因为Count 的返回值是 n 的引用 Count 栈帧销毁后访问变量 n 的空间此时n的空间已经还给操作系统了由于这是读操作编译器不一定检查出来但是本质是错的类似野指针访问。 2.如果 Count 函数栈帧销毁空间被清理那么取 n 值时取到的就可能是随机值取决于编译器的决策。 eg调用Count函数后再调用其他函数后会再次建立栈帧后面的栈帧会覆盖前面的栈帧恰好出现随机值 引用返回的原则如果函数返回时出了函数作用域返回对象还在(还没还给系统)则可以使用引用返回如果已经还给系统了则必须使用传值返回。区别就是传值返回生成拷贝引用返回不生成拷贝。
比如 static 修饰的静态变量就没有缺陷
int c()
{static int n 0;n;return n;
}
int main()
{int ret Count();cout ret endl;return 0;
}因为 static 修饰的变量在静态区出了作用域也存在这时就可以引用返回。
我们可以理解引用返回也有一个返回值但是这个返回值的类型是 int 中间并不产生拷贝因为返回的是别名。这就相当于返回的就是它本身。
引用返回还可以方便查找和修改-读写功能同在
#include cassert
#define N 10typedef struct Array
{int a[N];int size;
}AY;int PostAt(AY ay, int i)
{assert(i N);return ay.a[i];
}int main()
{AY ay;PostAt(ay, 1); // 修改返回值for (int i 0; i N; i){PostAt(ay, i) i * 3;}for (int i 0; i N; i){cout PostAt(ay, i) ;}return 0;
}由于PostAt 的形参 ay 为 main 中 局部变量 ay的别名所以 ay 一直存在这时可以使用引用返回。
引用返回 减少了值拷贝 不用将其拷贝到临时变量中返回并且由于是引用返回所以也可以 修改返回对象 。
总结如果出了作用域返回变量静态static全局变量上一层栈帧动态开辟malloc等不会随着函数调用的结束而被销毁的数据仍然存在则可以使用引用返回不能是函数内部创建的普通局部变量。 引用做返回值优点 1.修改获取返回值 2.减少拷贝提高效率(大对象/深拷贝对象–之后学习) 1.4常引用
上面提到引用类型必须和引用实体是同种类型的。但是仅仅是同种类型还不能保证能够引用成功我们若用一个普通引用类型去引用其对应的类型但该类型被const所修饰那么引用将不会成功。
int main()
{const int a 10;//int ra a; //该语句编译时会出错a为常量const int ra a;//正确//int b 10; //该语句编译时会出错10为常量const int b 10;//正确return 0;
}我们可以将被const修饰了的类型理解为安全的类型因为其不能被修改。我们若将一个安全的类型交给一个不安全的类型可被修改那么将不会成功。
const 修饰的 a 不能修改b 为 a 的引用。a 是只读但是引用 b 具有 可读可写 的权利该情况为 权限放大 所以错误了。下面没有错误是因为是一个拷贝d的改变不影响c
这时只要加 const 修饰 b 让 b 的权限也只有只读使得 权限不变 就没问题了 权限可以缩小此时x可以因为x本身有可以修改的权限且y、z的值同时也会变因为本来就是同一个空间x的改变就是y、z的改变。只是作为z时由于权限限制z不行
对于函数的返回值来说也不能权限放大例如
int func1()
{static int x 0;return x;
}int main()
{int ret func1(); // error return 0;
}这样也是不行的因为返回方式为 传值返回 返回的是临时变量具有 常性 是不可改的而引用放大了权限所以是错误的
这时加 const 修饰权限平移就没问题了const int ret func1()
同理这里错误的原因发生类型转换(提升、截断)的时候会产生一个临时变量 对于类型转换来说在转换的过程中会产生一个临时变量例如 int ii dd把dd转换后的值放到临时变量中把临时变量给接收的值ii而临时变量具有常性不可修改引用就加了写权限就错了因为 权限被放大了 。
而下图由于返回的是x的别名不是x不会产生临时变量了再传给int ret为权限平移总结对于引用引用后的变量所具权限可以缩小或不变但是不能放大指针也适用这个说法。const type 可以接收各种类型的对象变量、常量、隐式转换。对于输出型参数可以用引用反之用 const type 更加安全。
1.5引用和指针的区别
在语法概念上引用就是一个别名没有独立的空间其和引用实体共用同一块空间。
int main()
{int a 10;//在语法上这里给a这块空间取了一个别名没有新开空间int ra a;ra 20;//在语法上这里定义了一个pa指针开辟了4个字节32位平台的空间用于存储a的地址int* pa a;*pa 20;return 0;
}但是在底层实现上引用实际是有空间的从汇编角度来看引用的底层实现也是类似指针存地址的方式来处理的。 引用和指针的区别面试常考点强烈建议理解 1、引用在定义时必须初始化指针没有要求。 2、引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体。 3、没有NULL引用但有NULL指针。 4、在sizeof中的含义不同引用的结果为引用类型的大小但指针始终是地址空间所占字节个数32位平台下占4个字节。 5、引用进行自增操作就相当于实体增加1指针进行自增操作是指针向后偏移一个类型的大小。 6、有多级指针但是没有多级引用。 7、访问实体的方式不同指针需要显示解引用而引用是编译器自己处理。 8、引用比指针使用起来相对更安全。 2.内联函数
调用函数需要建立栈帧栈帧中要保存寄存器结束后就要恢复这其中都是有 消耗 的
int add(int x, int y)
{int ret x y;return ret;
}int main()
{add(1, 2);add(1, 2);add(1, 2);add(1, 2);add(1, 2);return 0;
}而针对 频繁调用 的 小函数可以用 宏 优化因为宏是在预处理阶段完成替换的并没有执行时的开销并且因为代码量小也不会造成代码堆积。
例如代码就可以写成这样
#define ADD(x, y) ((x) (y)) int main()
{cout ADD(1, 2) endl;return 0;
}但通过上图可以看出写宏时很容易出错下次再错就挨打吧[bushi]要么是替换出错要么是优先级出错所以宏并不友好。
为了减少函数调用开销还可以在一定程度上替代宏避免宏的出错 C 设计出了内联函数关键字为 inline
inline int add(int x, int y)
{int ret x y;return ret;
}int main()
{int ret add(1, 2);cout ret endl;return 0;
}2.1内联函数的概念
在 release 版本下inline 内联函数会直接在函数调用部分展开对于 debug 则需要 主动设置 (debug 下编译器默认不对代码做优化)但是 release 版本下其他版本优化的太多可能就不太好观察所以我们设置一下编译器在 debug 下看
打开解决方案资源管理器右击项目名称选中属性并打开在 C/C 区域常规部分在调试信息一栏设置格式为程序数据库
在 C/C 优化一栏将内联函数扩展部分选中只适用于 _inline
设置完毕后点击应用。
在设置前、后分别启动调试查看反汇编代码
修改前
修改后 两段反汇编代码最大的区别就是 call 消失了 call 就是函数调用的指令它的消失就说明第二段代码没有进行调用。内联函数直接在局部展开了在 main 函数中完成了操作。有了内联我们就不需要去用 c语言 的宏了因为宏很容易出错。
2.2 特性
inline是一种以 空间换时间 的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用。 缺陷可能会使目标文件变大优势少了调用开销提高程序运行效率。
inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建议将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰否则编译器会忽略inline特性。
inline不建议声明和定义分离分离会导致链接错误。因为inline被展开就没有函数地址了链接就会找不到。
注意 1空间换时间是因为反复调用内联函数导致编译出来的可执行程序变大
inline void func()
{// 编译完成为 10 条指令cout111111endl;cout111111endl;cout111111endl;cout111111endl;cout111111endl;cout111111endl;cout111111endl;cout111111endl;cout111111endl;cout111111endl;
}若不用内联函数不展开若10000次调用 func每次调用的地方为 call 指令的形式总计 10010 行指令。若用内联函数则展开若一千次调用每次调用的地方为都会展开为 10 条指令总计 10 * 10000 行指令。
展开会让编译后的程序变大如果递归函数作内联后果可想而知。所以长函数和递归函数不适合展开。
2编译器可以忽略内联请求内联函数被忽略的界限没有被规定一般10行以上就被认为是长函数当然不同的编译器不同
因此编译器会决策是否使用内联函数如果函数太大会造成代码膨胀。
3内联函数声明和定义不可分离
// 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 中被引用由于内联函数在调用的地方展开所以内联函数无地址这里的地址指的是call 指令调用函数的地址通过这个地址会跳到 jmp 指令处再根据 jmp 处指令跳转到函数执行的部分 即 f.cpp-f.o 的符号表中不会生成 f 的地址。
当编译时由于头文件要被包含但是这时只有函数声明但是没有函数的定义所以只能在链接时展开这里只能变为 call 地址的指令但是内联函数并没有地址链接不到就报错了。
所以当声明和定义分离调用函数时由于内联函数无地址编译器链接不到就会报错为链接错误。
// F.h
#include iostream
using namespace std;
inline void f(int i)
{cout i endl;
}// main.cpp
#include F.h
int main()
{f(10);return 0;
}因此。申明和定义不要分离直接在.h 文件中定义所有包含.h 的地方不需要链接直接展开
总结简短频繁调用的小函数建议定义成 inline 内联函数 . 1、inline是一种以空间换时间的做法省了去调用函数的额外开销。由于内联函数会在调用的位置展开所以代码很长或者有递归的函数不适宜作为内联函数。频繁调用的小函数建议定义成内联函数。 2、inline对于编译器而言只是一个建议编译器会自动优化如果定义为inline的函数体内有递归等编译器优化时会忽略掉内联。 3、inline不建议声明和定义分离分离会导致链接错误。因为inline被展开就没有函数地址了链接就会找不到。
3.auto关键字(C11)
3.1auto简介
在早期的C/C中auto的含义是使用auto修饰的变量是具有自动存储器的局部变量但遗憾的是一直没有人去使用它。 在C11中标准委员会赋予了auto全新的含义auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。
#include iostream
using namespace std;
double Fun()
{return 3.14;
}
int main()
{int a 10;auto b a;auto c A;auto d Fun();//打印变量b,c,d的类型cout typeid(b).name() endl;//打印结果为intcout typeid©.name() endl;//打印结果为charcout typeid(d).name() endl;//打印结果为doublereturn 0;
}
注意使用auto变量时必须对其进行初始化在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明而是一个类型声明的“占位符”编译器在编译期会将auto替换为变量实际的类型。
3.2 auto的使用细则
3.2.1 auto与指针和引用结合起来使用
用auto声明指针类型时用auto和auto*没有任何区别但用auto声明引用类型时必须加
#include iostream
using namespace std;
int main()
{int a 10;auto b a; //自动推导出b的类型为intauto c a; //自动推导出c的类型为int*auto d a; //自动推导出d的类型为int//打印变量b,c,d的类型cout typeid(b).name() endl;//打印结果为int*cout typeid©.name() endl;//打印结果为int*cout typeid(d).name() endl;//打印结果为intreturn 0;
}注意用auto声明引用时必须加否则创建的只是与实体类型相同的普通变量。
3.2.2在同一行定义多个变量
当在同一行声明多个变量时这些变量必须是相同的类型否则编译器将会报错因为编译器实际只对第一个类型进行推导然后用推导出来的类型定义其他变量。
int main()
{auto a 1, b 2; //正确auto c 3, d 4.0; //编译器报错“auto”必须始终推导为同一类型return 0;
}3.3 auto不能推导的场景
3.3.1auto不能作为函数的参数
以下代码编译失败auto不能作为形参类型因为编译器无法对x的实际类型进行推导。
void TestAuto(auto x)
{}3.3.2auto不能直接用来声明数组
int main()
{int a[] { 1, 2, 3 };auto b[] { 4, 5, 6 };//errorreturn 0;
}4.基于范围的for循环(C11)
范围for的语法糖 若是在C98中我们要遍历一个数组可以按照以下方式
int arr[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//将数组元素值全部乘以2
for (int i 0; i sizeof(arr) / sizeof(arr[0]); i)
{arr[i] * 2;
}
//打印数组中的所有元素
for (int i 0; i sizeof(arr) / sizeof(arr[0]); i)
{cout arr[i] ;
}
cout endl;以上方式也是我们C语言中所用的遍历数组的方式但对于一个有范围的集合而言循环是多余的有时还容易犯错。因此C11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围。
int arr[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//将数组元素值全部乘以2
for (auto e : arr)
{e * 2;
}
//打印数组中的所有元素
for (auto e : arr)
{cout e ;
}
cout endl;注意与普通循环类似可用continue来结束本次循环也可以用break来跳出整个循环。
4.1范围for的使用条件
4.1.1for循环迭代的范围必须是确定的
对于数组而言就是数组中第一个元素和最后一个元素的范围对于类而言应该提供begin和end的方法begin和end就是for循环迭代的范围。 以下代码就有问题因为for的范围不确定因为函数传参数组就会退化为指针
void TestFor(int array[])
{for (auto e : array){cout e endl;}
}4.1.2迭代的对象要实现和操作
这是关于迭代器的问题先了解一下。
5.指针空值nullptr
5.1 C98中的指针空值
在良好的C/C编程习惯中在声明一个变量的同时最好给该变量一个合适的初始值否则可能会出现不可预料的错误。比如未初始化的指针如果一个指针没有合法的指向我们基本都是按如下方式对其进行初始化
int* p1 NULL;
int* p2 0;NULL其实是一个宏在传统的C头文件(stddef.h)中可以看到如下代码
/* Define NULL pointer value /
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else / __cplusplus */
#define NULL ((void )0)
#endif / __cplusplus /
#endif / NULL /可以看到NULL可能被定义为字面常量0也可能被定义为无类型指针(void)的常量。但是不论采取何种定义在使用空值的指针时都不可避免的会遇到一些麻烦例如
#include iostream
using namespace std;
void Fun(int p)
{cout Fun(int) endl;
}
void Fun(int* p)
{cout Fun(int) endl;
}
int main()
{Fun(0); //打印结果为 Fun(int)Fun(NULL); //打印结果为 Fun(int)Fun((int)NULL); //打印结果为 Fun(int)return 0;
}程序本意本意是想通过Fun(NULL)调用指针版本的Fun(int p)函数但是由于NULL被定义为0Fun(NULL)最终调用的是Fun(int p)函数。
注意在C98中字面常量0既可以是一个整型数字也可以是无类型的指针(void)常量但编译器默认情况下将其看成是一个整型常量如果要将其按照指针方式来使用必须对其进行强制转换。
5.2 C11中的指针空值
对于C98中的问题C11引入了关键字nullptr。
注意 1、在使用nullptr表示指针空值时不需要包含头文件因为nullptr是C11作为关键字引入的。 2、在C11中sizeof(nullptr)与sizeof((void)0)所占的字节数相同。 3、为了提高代码的健壮性在后序表示指针空值时建议最好使用nullptr。
6.总结
今天我们认识并具体学习了有关引用、内联函数、auto关键字、范围for循环(C11)、指针空值nullptr的知识。接下来我们将继续学习C中类和对象的相关知识。希望我的文章和讲解能对大家的学习提供一些帮助。 当然本文仍有许多不足之处欢迎各位小伙伴们随时私信交流、批评指正我们下期见~
- 上一篇: 汉中网站建设费用微信商城小程序怎么开发
- 下一篇: 汉中网站建设开发wordpress add
相关文章
-
汉中网站建设费用微信商城小程序怎么开发
汉中网站建设费用微信商城小程序怎么开发
- 技术栈
- 2026年03月21日
-
汉中商城网站建设传奇新开网
汉中商城网站建设传奇新开网
- 技术栈
- 2026年03月21日
-
汉中公司做网站深圳网站品牌推广
汉中公司做网站深圳网站品牌推广
- 技术栈
- 2026年03月21日
-
汉中网站建设开发wordpress add
汉中网站建设开发wordpress add
- 技术栈
- 2026年03月21日
-
汉中网站制作今天《新闻联播》回放
汉中网站制作今天《新闻联播》回放
- 技术栈
- 2026年03月21日
-
杭seo网站建设排名深圳网站建设品牌策划
杭seo网站建设排名深圳网站建设品牌策划
- 技术栈
- 2026年03月21日
