• C++基础语法(篇二)


    前言:C++语法知识繁杂,要考虑的细节很多,要想学好C++一上来就啃书并不是一个很好的方法,书本的内容一般是比较严谨的,但对于初学者来说,很多概念无法理解,上来就可能被当头一棒。因此建议在学习C++之前学好C语言,再听听入门课程,C++有很多的语法概念是对C语言的一种补充,学习过C语言能更好的理解为什么要这样设计,笔者也是初学者,写的这类文章仅是用于笔记总结及对一些概念进行分析探讨,方便以后回忆,因知识有限,错误难以避免,欢迎大佬阅读指教

    目录

    引用

    引用的定义

    引用的用法  

    引用作为返回值出错情况

     引用与指针的区别

    内联函数

    内联函数的定义

    内联函数需要注意的点


    引用

    引用的定义

    不知道你是否有被指针给绕晕的情况,尤其是想改变指针的内容,那就要传二级指针过去,在使用指针的时候还要解引用,用着用着就蒙了。有时候会想能不能不用传指针过去就能够改变原变量空间的值呢?C++给我们提供了一种功能——引用,或许能帮助我们解决上面的问题

    引用其实就是给变量再重新取一个名字,引用并不会开辟一块新的空间,是和原变量同一块空间,就像叫你的小名和正式名,都是在叫你,总不能叫你的小名再叫你的正式名,就多出来一个你吧

    引用在创建的时候,是必须要有对象的,否则是不能创建的,这个很容易理解,压根就没这个人的时候,又怎么给取其它名字呢,使用引用时用符号 '&',一块空间可以引用多次,也就是有多个别名

    引用的用法  

    1.引用作为函数定义时的参数

    这样使用有类似指针的效果,不需要传指针过去,就能够改变原变量的值

    这样就不怕被指针给绕晕,还减少了空间的开辟,效率有所提高 

    使用引用时要注意权限的问题,在引用赋值中,权力可以缩小,但是不能扩大

    1. int main()
    2. {
    3. int a = 20;
    4. int& ta = a; //权限平移,不变
    5. const int b = 10;
    6. int& tb = b; //这里的tb的权限是高于b的,tb是左值,而b是右值
    7. //tb的权限扩大了。不可以
    8. const int b = 10;
    9. const int& mb = b; //这样权限就是相等的
    10. int b = 10;
    11. const int& nb = b; //这里tb的权限缩小了,这样是可以的
    12. return 0;
    13. }

    2.引用作为函数的返回值

    引用作为函数返回值是有前提条件的,我们都知道函数在创建的时候会开辟一个栈帧,调用结束后,栈帧会被销毁,内存返还给操作系统,既然栈里的内容都被销毁了,那么函数的返回值该怎么处理呢,事实上,函数调用结束后,如果有返回值,那么在栈帧销毁前,还会创建一个临时变量,用来保存最后的返回值

    问题的关键是,返回引用,返回的就是一个名字而已 ,引用指向的对象是在这个栈帧里的,栈帧一旦被销毁了,那么你返回一个名字又有什么用呢?这块空间已经不属于你了,就像有些人离开了这个世界,喊它的名字,它再也不会出现

    想要返回引用,那就要确保引用的对象在栈销毁后不会消失,比如静态区的全局变量以及堆区的变量,不过好处就是引用作为返回值效率是比传值高的

    引用作为返回值出错情况

    如果引用的对象的声明周期在栈中,那么在栈帧销毁后,再次使用引用变量会发生什么呢

    1.已被销毁的栈帧未来得及清理,那么有可能返回原来的值

    2.被销毁的栈帧已经被清理了,那么返回的就是随机值

    3.栈帧被销毁后,又开辟了其他的栈帧,已销毁的栈帧的数据被新开的栈帧的数据覆盖

    1. int& test(int x, int y)
    2. {
    3. int c = x + y;
    4. return c;
    5. }
    6. int main()
    7. {
    8. int& tmp = test(3, 4);
    9. test(10, 10);//再次调用后,就会将原先栈帧内的数据覆盖掉
    10. std::cout << tmp << std::endl;
    11. return 0;
    12. }

     引用与指针的区别

    1.指针的开辟是单独开辟一块空间,空间里存着指向对象的地址,而引用的开辟并没有单独开辟一块新空间,而是和原变量同一块空间

    2.引用在定义是必须要初始化,而指针没有要求

    3.引用在指定对象之后,就不能够再更换,而指针是可以更换的

    4.在sizeof()中的含义不同,引用则表示对象的大小,而指针的大小是有硬件决定的

    5.指针访问实体需要解引用,引用则是由编译器来实现

    6.有多级指针,但是没有多级引用

    7.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

    内联函数

    内联函数的定义

    什么是内联函数呢?在说内联函数之前,不妨先回忆一下C语言的宏定义函数,我们知道C语言的宏函数在预编译阶段就完成替换,省去了在栈中开辟函数的花销,运行速度也是刷刷的快。但是吧,宏函数的缺点也是很明显,首先就是不能够调试,还有就是容易引起歧义,要加不少的括号,阅读这种代码也非轻松之事,这些缺点也表明这是大佬的写法,为了更清晰的观察宏函数的缺点,我们写一个简单的宏函数来分析

    1. #include
    2. #define Add(x, y) ((x)*3 + (y))
    3. int main()
    4. {
    5. int a = 10;
    6. int b = 20;
    7. int c = Add(a, b);
    8. printf("%d ", c);
    9. }
    '
    运行

    1. #include
    2. #define Add(x, y) ((x)*3 + (y))
    3. int main()
    4. {
    5. //如果不给x加括号, (x*3 + (y)) 其传过去一个表达式,比如Add( 1+5, 10)
    6. int c = Add(1+5, 10);
    7. //那么在定义替换时将会是这样 ( 1 + 5 *3 + (10) ) 这就产生了歧义
    8. printf("%d ", c);
    9. //如果把最外面的大括号去掉, (x)*3 + (y) 然后调用之后乘以3
    10. int c = Add(5, 10) *3;
    11. //那么在定义替换时将会是这样 (5) *3 + (10) *3 这就又产生了歧义
    12. printf("%d ", c);
    13. }

    由上面的例子,我们能发现,宏函数任何一对括号可能都是有道理的,这就导致写不好写,看不好看,还不能调试,可即使缺点不少,仍耐不住它快这个优点,要是宏函数能像普通函数一样可以调试,不用注意那么多细节就好了。还真有!大佬们在C++中加入了内联函数这个概念就是为了解决这种问题,内联函数就是在定义普通函数时加上关键字 inline ,加上这个关键字之后,你可以接着编写你的函数,其他的不用管,当编译器识别到 inline 时,会考虑将你编写的这个函数给优化成宏函数,注意:加上inline只是建议编译器将其优化成宏函数,并不是一定会优化成,如果你的函数很长很复杂,本身就不适合转换成宏,那么编译器可以选择忽略建议

    内联函数需要注意的点

    所以说内联函数适合短小简明的函数,有效利用才会提高效率,事实上,如果你频繁调用某个内联函数,那么内联函数可能就是在用空间换取时间了,什么意思呢?接着用刚才的那个例子来看看

    1. #include
    2. using namespace std;
    3. inline int Add(int x, int y)
    4. {
    5. return x+y;
    6. )
    7. int add(int x, int y)
    8. {
    9. return x+y;
    10. }
    11. int main()
    12. {
    13. int c = Add(10, 10);
    14. int d = Add(10, 10);
    15. int e = Add(10, 10);
    16. int f = Add(10, 10);
    17. int g = Add(10, 10);
    18. int h = Add(10, 10);
    19. int i = Add(10, 10);
    20. int j = Add(10, 10);
    21. int k = Add(10, 10);
    22. }

    分别用内联函数和普通函数实现两数相加,因为内联函数会将其优化成宏函数,在编译的时候全部展开到调用函数的地方。我们写的比较简单,假设 Add 和 add 里面都有10个语句,同时调用两个函数各一万次,那么内联函数在展开时,总共有1w * 10 个语句,而普通函数则有1w + 10 个语句,显然内联函数的代码所占用的空间是更多的,不过其速度仍然是很快

    内联函数的声明和定义是不能够分开的,什么意思呢?我们平时喜欢把函数的声明放在头文件里,然后函数的实现放在另一个头文件里,但对于内联函数来说,这样分开放是不行的,声明和定义须放在一起,为什么呢?

    原先你在 test_a.h 中声明一个函数,在test_b.c中定义该声明的函数, 在test_c.c中调用这个函数,但是这三个文件是独立的,彼此都不认识彼此,最终又是如何在调用的时候找到函数定义的呢?

    还记得我们之前提到过,C/C++在编译阶段,每个文件在编译的时候都会进行词法和句法的分析,将定义的符号提取生成一张符号表,函数名或者函数的地址都放到这张符号表上,比如test_a.h 中只有函数的声明,没有函数的定义,那生成的符号表,在函数地址那一块就是无效的,不过之后会再进行链接,将生成的多个符号表进行合并,合并后会保留有效的函数名和有效的函数地址,通过最终的这个符号表就能够找到函数的定义和函数的声明

    一般函数都是按上面的方法编译的,但是宏函数不一样,因为宏函数是在预编译阶段就完成替换,就相当于把函数的代码直接展开到调用处,不需要在栈区开一个栈帧,不需要函数地址,因此宏函数的函数名是不会放到符号表中的,而内联函数的目的就是建议将该函数转化成宏函数,所以,无论这个函数最终有没有被替换成宏 ,只要加上 inline 它都不会在符号表中出现

    假如你在 test_a.h 中声明一个内联函数   inline int Add( int x, int y),在test_b.c中定义  inline int Add(int x, int y ),然后在test_c.c中调用 Add(10, 10 ) ,这个时候编译器就会报未定义的错误

    因为内联函数不会进入到符号表,而main函数只包含了声明文件 "C++test.h",也就是说main函数会通过这个函数的声明去符号表里查找这个函数,当然是查找不到的了,所以就报了未定义的错误,如果我们直接包含" C++test.cpp" 这个函数就能够跑起来,因为" C++test.cpp"的内容会直接展开到main这个文件里,所以在定义内联函数时,不要将定义和声明分离开,需要写在一起

  • 相关阅读:
    服务端Skynet(五)——如何搭建一个实例
    java计算机毕业设计科技项目在线评审系统源码+mysql数据库+系统+lw文档+部署
    pdf转换器哪个软件好用还免费?来这里获得实用技巧!
    MySQL是如何保证数据不丢失的
    网工内推 | IT工程师,IA认证即可,五险一金,全勤奖,最高10k
    无线通信的未来:WiFi HaLow的低功耗、超长距离革命
    JavaScript-BOM编程
    工业控制系统协议的安全性
    【重拾C语言】十一、外部数据组织——文件
    7-VMware Horizon 2203 虚拟桌面-事件数据库配置(七)
  • 原文地址:https://blog.csdn.net/m0_61350245/article/details/126975191