C/C++重难点总结系列(一)

前言

该系列是本人学习C/C++以来,阅读相关书籍以及编程实践中记载的笔记中提炼的知识点,现整理成博文与大家分享。本系列不是小白系列,尽量避免冗杂的解释和叙述,简明扼要地总结干货,适合进阶的童鞋。限水平有限,有任何问题欢迎大家指正和讨论。

------------------------------------------------------------------------------------------------------------------------

参考书目:

《C和指针》

《C陷阱与缺陷》

《C++ Primer》 第五版

《Effective C++》第三版

《深度探索C++对象模型》

-------------------------------------------------------------------------------------------------------------------------

持续更新中。。。

现更新到《C/C++重难点总结系列(五)》

相关博文连接:

http://blog.csdn.net/hechao3225/article/details/52727571

http://blog.csdn.net/hechao3225/article/details/52728735

http://blog.csdn.net/hechao3225/article/details/54143094

http://blog.csdn.net/hechao3225/article/details/71155659

--------------------------------------------------------------------------------------------------------------------------

1.关于程序的内存区域

(1)代码区:存放程序的二进制代码。

(2)全局区:存放全局数据和静态数据,编译器编译时即分配内存。全局变量静态变量的存储是放在一块的。对于C语言初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。而C++则没有这个区别,程序结束后由系统释放。

(3)文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放。

(4)栈:程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。内存中栈的地址向低地址生长,其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。

(5)堆:内存中开辟的另一块区域,交给程序分配和释放,故又称动态内存内存中堆的地址向高地址生长,若程序员分配的动态内存不及时释放会导致内存泄露(运行时动态内存耗尽),程序结束时一般会由OS回收 。

2.关于函数的调用机制

(1)调用过程:建立函数调用栈---保存调用函数的运行状态和返回地址---传参---进入被调函数。

(2)传参机制:传参不一定会压栈!fastcall(快调)一般会由寄存器传参,另外当参数不超过4个时一般也由寄存器传参,否则会压栈。压栈顺序一般从右到左

3.关于三种传参方式的比较

C/C++中传参方式可分三种:值传递(传副本)、指针传递(传地址)、引用传递(传别名)。

 (1)值传递其实是一种赋值操作,实参传递给形参时会产生额外的副本,形参只是实参的拷贝,所以函数内对形参的操作不会更新到源实参。在函数结束时,形参作为局部变量会被释放,对实参不会产生任何影响。若为类的对象会调用拷贝构造,这种深拷贝操作会影响到传参效率。(可理解为“单向接口”)

 (2) 指针传递:因为传递的是实参的地址,指针指向的内存中同一个对象,所以函数内部对形参得操作会“同步更新”到实参。(可理解为“双向接口”)

 (3)引用传递(C++特有):传递的是实参的别名,传参时形参被绑定到实参对象上,因此函数内部对形参的操作也都会“同步更新”到源实参。(可理解为“双向接口”)

   注:关于指针传递和引用传递,形参改变对实参的改变是一种副作用,我们利用这种副作用可以方便修改传递的实参,另外,这种方式避免了值传递中的拷贝,对于大数据的实参可以有效提升效率。因此,对于想提升传参效率又不想影响实参的情况,可以使用const:

[html] view plain copy
  1. bool isShorter(const string &s1,const string &s2)//const修饰指针传参会有同样的效果  
  2. {  
  3.        return s1.size()<s2.size();  
  4. }//这也是为何在我们不想不修改实参时尽量使用const引用(指针)而不是值传递的原因。  
4.关于static用法

(1)静态局部变量:加上static的局部变量,存放在全局数据区,故延长了生命周期,直到程序结束才会销毁。重入函数时不会再次初始化,而是沿用上一次的值,这点也是静态局部变量的常用原因。

注:静态局部变量!=全局变量,二者生命周期相同,但作用域不同,静态局部变量只对函数体内部可见。

(2)外部静态变量/函数:

static修饰全局变量:限定该变量只在该文件中可用。

static修饰外部函数:往往在函数声明中加static修饰,限定该函数只在该文件可用(若在头文件中声明,则限定只在其对应的源文件中可用)

(4)静态成员变量/函数:类中static修饰的成员。静态成员与类的对象实体无关,是该类的共享变量。

注:易错点:静态成员变量在类中static声明后还需要在类外定义!否则编译器报错未定义。

5.关于extern

(1)extern修饰函数:C/C++头文件中的函数声明默认为extern,即外部可用(其他源文件只需包含头文件即可使用)。和static修饰的效果相反。

(2)extern修饰变量:带extern的变量仅仅是声明而不是定义!用extern使变量可以在多文件中共享,主要有两种做法:

         a.在源文件中定义,其他需要使用该变量的源文件用extern声明。(表示该变量在其它文件中定义,即一次定义,多次extern声明)

  b.在源文件中定义,其对应的头文件中extern声明,其他需要使用该变量的源文件包含该头文件即可。(更加标准的做法)

6.关于递归的安全性
  递归过程其实是反复压栈、出栈的过程,递归进入时会压栈,每递归一次,栈内存会多一截,递归逐级返回时即出栈。设想栈深度有限,而递归次数过多或无限递归时,必然导致栈溢出

7.关于内联

用途:定义或声明函数时返回值前加上inline修饰能避免函数调用带来的时间和空间开销,提高效率,适用于反复执行的核心代码。内部实现机制其实是编译时按函数体展开代码,避免了函数调用的一系列压栈出栈过程。

限制:(1)不能出现复杂的控制结构语句;

           (2)递归函数不能用作内联函数;

           (3)内联函数体不宜代码过长,只适合数行的小函数。

注:1.内联和宏定义均属于代码替换机制,但前者安全性更好,宏只是预处理做简单的符号替换而不会做类型检查。内联可以完全替代宏,反之不能。

       2.inline关键字只是一种“建议”,是否采用内联机制取决于编译器。

8.关于重载

用途:C++特有机制。让同一种算法针对不同类型使用相同的函数名,提高代码可读性,重载的函数至少在参数个数或类型上有所区别,仅返回值区别不能重载!如:

[html] view plain copy
  1. void func(int);和int func(int);//编译器无法区分  
  2. int abs(int);和double abs(double);//C++通过名字粉碎技术自动匹配  

注:1.重载和重写(覆盖)是两个完全不同的概念,重写是面向对象中子类对父类虚函数的重新实现!各大互联网公司笔试、面试常考题,然很多程序猿经常混为一谈。

2.底层const修饰的参数以及常量成员函数也可以重载。

9.关于默认参数

用途:C++特有机制。函数声明时加入默认值,调用时可忽略该参数直接使用默认值,提高编码效率。默认值可以是常量、全局变量、函数等。

限制:某个默认参数的后面所有参数均需要默认值,因此经常把带有默认值的参数放在其他参数后面。

10.关于字符指针、字符串常量和字符数组名

      字符指针、字符串常量和字符数组名属于同一种数据类型!都是地址 !  两字符串直接比较实为比较地址,应调用strcmp()比较。打印字符指针就是打印字符串,打印解引用则输出单个字符。如:

  1. char *p="hello";  
  2. cout<<p<<endl;//打印整个字符串  
  3.   
  4. char*p="hello";  
  5. while(*p!='\0')  
  6.      cout<<*p++<<endl;//逐个打印字符  

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页