c++知识理解(#define, 堆/栈内存,static, const关键字,引用与指针,多态)

1、宏定义

宏定义命令 #define ,

1)不带参数的宏定义

格式:#define 标识符 字符串,其中标识符即为宏名。

作用:在预编译阶段进行宏展开,将宏名替换为定义的字符串。不做语法检查。

说明:a. 宏名一般大写,末尾无分号

           b. 宏定义不分配内存,变量定义分配内存

           c. 宏定义只对标识符进行替换,不会将包含标识符的字符串进行替换。

例: #define PI 3.1415926  

//把程序中出现的PI(标识符)全部换成3.1415926,若程序包含字符串“PI",也不会发生宏替换。

2)带参数的宏定义

格式:#define 宏名(参数list) 字符串。

作用:类似函数调用,但有一个哑实结合(参数传递)的过程。

说明:a. 实参是表达式时需特别注意

              #define S(r)  r*r

              int area = S(a+b);    // 宏展开结果 int area = a+b*a+b;

              正确定义应为: #define S(r)  (r)*(r)

          b. 宏名和参数list之间的括号间不能有空格,因为宏定义整体只有3个字段,#define字段,宏名(参数list)字段,字符串字

              段。如果有空格,即写成 #define S (r)  (r)*(r)形式,会compile error,r not declared。 

          c. 宏展开(替换)和函数调用的区别

             1. 宏替换只做替换,不做计算和表达式求解。

             2. 宏替换在编译前运行,不分配内存;函数调用在编译后,程序运行时进行,并分配内存。

             3. 宏的哑实结合(传参过程)不存在类型,也没有类型转换。

             4. 宏展开使源程序变长,而函数调用不会。

             5. 宏展开不占运行时间,只占编译时间;函数调用占用运行时间(分配内存,保存现场,传参,返回值)

应用示例:1. 不用循环和递归,实现打印数字0到999[1]。

#include 
#include
using namespace std;

#define A(x) x;x;x;x;x;x;x;x;x;x;

int main(void)
{
    int n = 0;

    A(A(A(printf("%d\n", n++)))); //A(printf("%d\n", n++))替换为10个输出语句, A(A(A()))1000个

    system("pause");
    return 0;
}

2、有参宏定义中#和##的用法。

#:将参数两端加上字符串的""符号。

例:#define S(str) #str

       S(name)    //替换为"name"

##: 字符串连接

例:#define S(str) p##str

      S(name)    //替换为pname

3、预处理器命令

#define    定义宏

#include包含一个源代码文件

#undef    取消已定义的宏

#ifdef    如果宏已经定义,则返回真  (#ifdef, #ifndef可用来防止头文件重复包含)

#ifndef    如果宏没有定义,则返回真

#if    如果给定条件为真,则编译下面代码

#else    #if 的替代方案

#elif    如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码

#endif    结束一个 #if……#else 条件编译块

#error    当遇到标准错误时,输出错误消息

4、栈内存和堆内存

在c++中,内存分为5块,堆、栈、自由存储区、全局/静态存储区和常量存储区。图片来自[2]

c++知识理解(#define, 堆/栈内存,static, const关键字,引用与指针,多态)_第1张图片

堆和栈的区别[3]:

1)、管理方式:堆资源由程序员控制(通过malloc/free、new/delete,容易产生memory leak),栈资源由编译器自动管理。

2)、空间大小:堆是链表存储的不连续区域,受限于计算机的虚拟内存;栈是连续区域,为OS设置好的。

3)、碎片问题:对于堆,频繁的申请/释放内存会造成大量内存碎片,降低效率;对于栈内存,由于FILO结构,不产生碎片。

4)、生长方向:堆向上,向高地址方向增长;栈向下,向低地址方向增长。

5)、分配方式:堆是动态分配;栈有静态分配(如局部遍历)和动态分配alloc函数(变长数组)。

6)、分配效率:堆是C/C++函数库提供的,机制复杂;栈是机器系统提供的数据结构,进出栈都有专门的指令,效率高于堆。

5、static关键字作用

1)修饰变量:

     a. 修饰全局变量时,改变全局变量的作用域,使得变量只在本文件可见,对其他文件隐藏。

     b. 修饰局部变量时,改变局部变量生存期。只做一次初始化,保持变量内容的持久。在函数内部定义的static局部变量,生存

         期为真个源程序,但是作用域仍然限制在函数内部。

     c. 修饰类的成员变量。static成员变量时整个类共有,属于类而不属于对象。i.e. 统计该类创建的对象的个数。

        class A{

         static int count = 0;

         }

 2)修饰函数

      a. 修饰普通函数时,使得函数只在本文件可见,对其他文件隐藏。

      b. 修饰类的成员函数时,类的静态成员函数属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态           数据和静态成员函数

例:在类内部定义static count = 0, 在构造函数count++,可用于统计创建的对象的数量。

     修饰成员函数。static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量

6、引用和指针的区别[5]

作用:都是间接引用其他对象。

不同:1)指针指向引用对象内存地址,而引用相当于对象的别名,指针可以为空,但引用不可以为空。所以引用在定义时必须初

                始化,但指针不必。另外,引用使用时不用判断非空,因此效率比指针高。

           2)指针可以被重新赋值以指向另一个不同的对象,但是引用不可以,总是指向初始化时指定的对象。

7、c++类的继承和多态

1)基础知识

多态,即多种形态,当类之间存在层次,且通过继承关联时,会用到多态。c++多态是指在调用函数时,会根据调用对象的类型调用不同的函数

多态分为两种:静态多态/静态链接/早绑定(覆盖)动态链接(多态)

静态链接/覆盖:基类中定义非虚函数,派生类中定义了同名同参同返回类型的函数。

派生类对象调用函数,为派生类中定义的那个(覆盖了基类的实现)

动态链接(多态):基类中定义virtual 函数,派生类中定义了同名同参同返回类型的函数。实现动态绑定。基类类型对象调用函数为基类实现,继承类对象为继承类实现。具体调用哪个实现由实际运行时对象类型决定。

例[6]:

静态链接

基类                                                                                               

c++知识理解(#define, 堆/栈内存,static, const关键字,引用与指针,多态)_第2张图片

继承类                      

c++知识理解(#define, 堆/栈内存,static, const关键字,引用与指针,多态)_第3张图片              

main()

c++知识理解(#define, 堆/栈内存,static, const关键字,引用与指针,多态)_第4张图片

输出为“Parent class area:",调用的是基类的area()

动态链接-将基类area()定义为virtual

c++知识理解(#define, 堆/栈内存,static, const关键字,引用与指针,多态)_第5张图片

输出为“Rectangle class area:",调用的是继承类的area()。

虚函数: 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

纯虚函数:virtual int area() = 0; 在基类中不给出实现

2)什么函数不能声明为虚函数?[7]

虚函数主要是用来在类的继承时实现多态性(在基类声明为virtual函数,在派生类中再次定义函数,告诉编译器不要静态链接到该函数)。

所以,不能被继承和重写的函数不能声明为虚函数。具体地,普通函数,友元函数(不能被继承),静态成员函数,内联成员函数,构造函数均不可声明为虚函数。

8. const关键字[8, 9]

可修饰内置类型变量,自定义对象,成员函数,函数返回值,函数参数

1)修饰普通变量-值不可修改

const int a = 8;

a = 9; // error

int *b = (int *)&a;

*b = 9; //error.

2)修饰指针变量

const int * p1 = 8;   //指针内容8不可改变

*p1 = 5; //error

int a = 8;

int * const p2 = &a;   //指针指向的地址不可变

*p2 = 5;    //right

int b = 5;

p2 = &b;    // error

const int* const p3 = &a;  //内容和指向均不可改变。

*p3 = 0;     //error

p3 = &b;    //error

口诀: “左定值,右定向,const修饰不变量”

3)参数传递和函数

参数传递分为3种情况:a. 值传递。一般不用const,因为函数会自动产生临时变量复制参数的值

                                          b. 指针传递,可防止指针被意外篡改。

                                               i.e., void fun( int * const p){}

                                           c. 引用传递。对于自定义类型的参数传递,需要临时对象复制参数,需要调用构造函数,比较浪费时间,所

                                              以一般用const type &的方式,防止传入的实参被修改。但对于内置类型int, double等,一般不用引用传递。

修饰函数分2种情况:a. 修饰返回值。

                                           用const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根

                                           究底就是使得函数调用表达式不能作为左值。如:const A& action (A a);

                                       b. 成员函数后加const,声明函数为只读函数,不能改变类内的数据成员。注意:const函数不能调用非const

                                       i.e., class Account{

                                              int count;

                                              int get_count() const

                                              { return count; }

                                              }

 参考资料:

[1]https://www.cnblogs.com/si-lei/p/9394399.html

[2]https://www.cnblogs.com/ChenZhongzhou/p/5685537.html

[3]https://www.cnblogs.com/yiluyisha/p/9049051.html

[4]https://blog.csdn.net/ll148305879/article/details/92794360

[5]https://www.zhihu.com/question/37608201

[6]https://www.runoob.com/cplusplus/cpp-polymorphism.html

[7]https://blog.csdn.net/judgejames/article/details/87914127

[8]https://www.cnblogs.com/Forever-Kenlen-Ja/p/3776991.html

[9]https://www.cnblogs.com/fan-0802-WHU/p/10961279.html

 

 

     

 

  

 

 

 

你可能感兴趣的:(c++)