C++八股文(基础面试题)

目录

1、c++中内存的分布情况

2、栈和堆的区别

3、C++如何申请空间

4、new和malloc的区别

5、为什么有了malloc/free,后还需要new/delete

6、函数传递参数的几种方式

7、C++中指针参数的传递和引用参数的传递

8、指针和引用的区别

8.1、指针和数组的区别 

9、函数指针

10、野指针和悬挂指针,并如何避免

11、const修饰指针

12、static定义静态变量

13、const定义常量

13.2、const和volatile

14、C/C++编译过程

15、宏定义#define和常量const的区别

16、C和C++的区别

17、C++中struct和class的区别

18、C++中重载和重写、重定义的区别

19、类中的所有构造函数

20、类中的析构函数

21、编译器提供的默认函数

21.2、类中构造函数调用的顺序,和析构的顺序

22、C++面向对象的三大特性

23、类的继承权限问题

23.1、菱形继承问题:

24、C++中<>和" "引入头文件的区别

25、虚函数的作用:(virtual关键字修饰)

26、虚函数和虚函数表

27、那些函数不能是虚函数:

28、析构函数为什么要写成虚函数

29、纯虚函数和抽象类

30、浅拷贝和深拷贝

31、调用拷贝构造函数的时机

32、拷贝构造函数为什么要是引用传递,而不能是值传递

33、内存泄漏

34、#define和inline的区别

35、#define和typedef的区别

36、交换两个变量的值


1、c++中内存的分布情况

(1)、栈:由编译器管理分配和回收,用于存放函数参数,局部变量等等

(2)、堆:由程序员自己管理,主要通过new, delete, malloc, free进行分配和回收,空间比较大,但有可能会存在内存泄漏和空闲碎片的情况

(3)、全局/静态区:分为初始化和未初始化两个相邻的区域,存储初始化和未初始化的全局变量和静态变量

(4)、常量区:用于储存常量,一般不允许修改

(5)、代码区:用于存放程序的二进制代码

2、栈和堆的区别

栈:由编译器进行管理,在需要的时候分配空间,在不需要的时候自动回收空间,一般用于存放函数参数和局部变量,连续的存储空间。一般入栈顺序是从右到左入栈。

堆:由程序员自己进行管理,在需要时通过new或malloc申请分配空间,在不需要的时候通过delete或free回收空间。如果不回收释放,则会存在内存泄漏的问题。不连续的空间。实际上,在分配内存时,系统中有一个空闲的内存链表,当程序员申请时,系统会遍历空闲链表,找到第一个内存空间大于或等于申请的空间分配给程序,一般在分配内存时,也会在内存头部写入内存的大小,目的是方便delete回收。

3、C++如何申请空间

C/C++主要是在堆区申请空间,主要通过new和malloc申请,然后通过delete或free释放回收内存。

4、new和malloc的区别

(1)、new和delete是C++关键字,而malloc和free是C语言库函数。都用于申请动态内存和释放内存。new的底层实现,也是基于malloc实现的。

(2)、new在调用时,是先分配内存,再调用构造函数,delete在调用时先使用析构函数,再释放内存,而malloc只分配内存,free只释放内存。故而,new比malloc更安全,因为,他会调用构造和析构函数。

(3)、new申请内存不需要计算申请内存的大小,且不需要强制类型转换,其返回的类型就是对应申请类型的指针,而malloc申请时,需要手动计算申请内存的大小,并且还需要强制类型转换为需要的类型,因为malloc返回的类型为 void*。

(4)、new申请内存并初始化,malloc申请内存,但不初始化。

(5)、new申请内存失败,返回NULL,而malloc申请内存失败,则返回异常。

5、为什么有了malloc/free,后还需要new/delete

        因为对于非内部数据类型而言,光malloc/free并不能满足动态对象的要求,对象在创建和消亡时,会自动调用构造函数和析构函数,而malloc/free是库函数而非运算符,并不在编译器的控制权限内,故不能把构造和析构的任务交给malloc/free,故而有了new/delete。

6、函数传递参数的几种方式

(1)、值传递:形参是实参的拷贝,形参的改变并不影响实参

(2)、指针传递:也是值传递的一种,不过形参接收的是实参的地址,对形参所指对象进行操作,及等价于对实参的操作

(3)、引用传递:实际上就是把引用对象的地址放在了所开辟的栈空间中,函数对其形参的操作可以直接映射到实参上

7、C++中指针参数的传递和引用参数的传递

(1)、指针参数传递:本质是也是值传递,只不过传递是地址值,被调函数的形参被作为被调函数的局部变量处理,会在栈中开辟空间,用来存放主调函数传入的实参值拷贝值,及实参的一个副本。故当形参的指向没有变化时,对所指向的操作既是对实参的操作,若形参指向改变,则无法操作实参

(2)、引用参数传递:被调函数的形参也是作为被调函数的局部变量,会在栈区申请内存空间,但存放的是实参变量的地址,故而对形参的任何操作都会直接影响到实参,即可以通过栈中的地址找到实参变量。

(3)、两者的不同:虽然两者都是被调函数栈空间上的一个局部变量,但是对于任何引用参数的处理,都会通过间接寻址的方式操作到实参,而指针参数的传递,则是,如果改变了被调函数中的指针地址,则形参的操作无法再影响到实参。因为引用对象的指向不可以更改,而指针的指向可以更改。

8、指针和引用的区别

(1)、指针和引用都是一种内存地址的概念,区别在于,指针是一个实体,而引用只是一个别名。

(2)、指针:指针指向的是一块内存地址,所指向的内容是内存的地址,但指针所指向的值是可以改变的,允许拷贝和赋值,有const和非const的区别,可以为空,sizeof指针得到的也是指针类型的大小。

(3)、引用:对于引用而已,引用只是一块内存的别名,引用必须在定义时绑定到一块内存上,即引用必须初始化,后续不可更改,也不能为空,且没有const和非const的区别。sizeof引用得到的是所初始化对象的大小。

(4)、指针必须在解引用后才能对对象进行操作,而引用可以直接对对象进行操作。做为参数来说,指针实质上是值传递,传递的是地址在,而引用实在上是地址传递,传递的变量是地址值。

8.1、指针和数组的区别

(1)、数组是用于存储多个相同类型的数据集合,数组名是首元素的地址,sizeof的大小不一定。

(2)、指针:相当于一个变量,存储的是内存中的地址,sizeof的大小固定。

(3)、区别:同类型的指针可以相互赋值,而数组不行,数组只能一个一个元素的赋值或拷贝。

(4)、数组所占内存空间的大小:sizeof(数组名)/sizeof(数组数据类型);指针所占内存空间的大小:sizeof(指针名),在32位系统中占4字节,在64位系统中占8字节。

9、函数指针

(1)、定义:函数指针就是指向函数的指针,函数指针首先就是一个指针,该指针指向的是一个具体的函数,在编译时,每个函数都有一个入口地址,函数指针所指向的就是该函数的入口地址。有了指向函数的指针后,后面就可以用该函数指针调用该函数。

(2)、用途:调用函数或做为函数的参数。

10、野指针和悬挂指针,并如何避免

(1)、野指针:就是没有经过初始化的指针。

(2)、悬挂指针:就是最初指向的内存被释放后,未被置空的指针。

(3)、无论是野指针还是悬挂指针,所指向的都是一块无效内存的指针,访问无效内存,将导致程序编译出错。

(4)、避免:对于野指针的避免,就是在定义指针后且在使用之前对指针进行初始化,或用智能指针。对于悬挂指针的避免,就是在内存释放后,即使的吧指针置空或调用智能指针。

11、const修饰指针

(1)const  int  *p = &a 常量指针,指向可以改,但指向的值不可以改。

(2)int  * const p = &a 指针常量,指向不可以改,但指向的值可以改

(3)const int * const p = &a const同时修饰指针和常量,指向和值都不可以改。

12、static定义静态变量

(1)、作用:控制变量的存储方式和作用域(生命周期和作用范围)

(2)、static修饰局部变量:对于一般局部变量而已,一般存放在栈区,且局部变量的生命周期在所包含的语句块结束后而结束,而通过static修饰后的局部变量,则该局部变量存放在静态区,生命周期会一直延续到整个程序执行结束后而结束,但其作用域未发生改变,作用的还是其所在语句块中。

(3)、static修饰全局变量:对于全局变量,一般存放在全局区,能够被整个程序所访问到,同时也能被同一个工程中的其他源文件所访问到(但是需添加extern声明),通过static修饰过后,则该全局变量存放在静态区,且也改变了该变量的作用域,通过static修饰后,该全局变量只能在本文件中被访问到,而其他文件访问不到。

(4)、static修饰函数:与修饰全局变量一样,改变了其作用域。

(5)、static修饰类:如果类中的某个成员函数被static所修饰,那么该成员函数不属于该类的任何一个对象,他属于整个类,所有对象共享同一个函数,同时该函数也只能访问类中的静态变量,而不能访问其他成员变量和成员函数。如果static修饰类中的成员变量,那么该变量就归所有的对象,存储空间中也只有一个副本,所有对象共享同一份数据,可以通过类或对象直接进行调用。

13、const定义常量

(1)、const修饰基本数据类型:使得这些变量变为常量,其值不可更改。

(2)、const修饰指针或引用:const * 则const修饰的是变量,*const则const修饰的是指针。

(3)、const作用到函数:const作用到函数中时,对其函数作用部分进行常量化,当用作函数参数时,其参数被定义为接收常量,函数体内则无法改变其参数,这样就可以保护原对象的变量不会被函数所改变(一般作用参数的是指针或引用)。

(4)、const在类中的用法:const修饰成员变量,该变量只在该对象中是常量,其值不可以改变,但不同的对象可以申请不同的常量值,所以不能在类中初始化const修饰的成员变量,因为类的对象在没有创建时,编译器不知道const修饰的成员变量的值是什么,const修饰的成员变量的初始化只能在构造函数中初始化。const修饰成员函数,主要是为了防止成员函数修改其对象的内容(就是成员函数不可以更改成员变量),若成员函数想修改某个变量,则可以将该变量用mutable进行修饰。

(5)、const成员函数和static不可同时使用,因为static修饰的成员函数不包含this指针,且不能实例化,而const成员函数需要具体到某一个函数。

(6)、const修饰类:定义常量对象,常量对象只能调用常量函数,不可以调用其他函数。

13.2、const和volatile

(1)、const:定义常量,其本质是设置变量为只读,编译器不允许程序通过该变量去改变该变量的值,从而达到了对该变量的保护,但也可以通过其他方式改变其变量的值,如通过指针,指向变量的地址,从而改变变量的值。

(2)、volatile:表示对象会被当前执行的代码流之外的东西所修改,如另一个线程、中断处理函数或硬件修改等(简单来说,就是该变量在代码中不去修改,在代码外的其他地方也有可能会被其他东西所修改),变量被volatile修饰的时候,编译器在读取该变量的值时,每次都会去内存地址中重拿一次,即使他前面的指令刚刚读取过这个值(即表示一个只能读取,但又一直在变的对象)。即减少优化。

(3)、一个参数既可以是const又是volatile:该参数表示的是即不能被程序所修改,也不能被优化到寄存器。但可以通过代码外的其他方式修改其变量,如实时时钟、只读状态寄存器等等。

14、C/C++编译过程

预处理(展开宏和内联函数)->编译(编译成汇编语言)->汇编(编译成二进制文件)->链接(链接其他所包含的库文件)

15、宏定义#define和常量const的区别

(1)、类型和安全检查不同:宏定义只是字符的替换,没有数据类型的区别,同时这种替换也没有类型安全检查,而const常量是常量的声明,有数据类型的区别,同时在编译阶段也会有类型的检查。

(2)、编译器处理的不同:宏是一个“编译时”概念,编译器在预处理阶段就把宏给展开,不能对宏进行调试;而const是一个“运行时”概念,编译器在编译阶段处理const变量,类似于一个只读数据。

(3)、存储方式的不同:宏定义是直接替换,没有内存分配,存储于代码段中;而const常量有内存分配,存储于数据段中。

(4)、定义域不同:宏定义是全局定义,可以在整个程序中使用,而const是一个局部定义,只能在定义的地方使用。

16、C和C++的区别

(1)、语法上:语法上C++的区别有头文件和命名空间的不同,C++允许我们自定义自己的空间,而C不可以;在关键字方面也有不同,如C++在动态内存管理上增加了new和delete,在指针上,C++增加了引用的概念,C++在关键字上,也还增加了auto类型等等。

(2)、函数上:C++支持函数重载,而C不支持,主要原因是C++函数的名字修饰和C不同,C++在函数名字修饰时,会把参数加在后面,如 int a(int b, char c) 会修饰成 _a_int_char,而C只会修饰成_a,所有C++中支持不同参数调用不同的函数,即支持函数重载;  C++还有虚函数的概念,用以实现多态。

(3)、struct上:C的struct像是一个数据结构的集合,而C++的struct不仅成员函数,还有成员变量,同时还增加了访问权限的概念。

(4)、C是面向过程的语言,而C++是面向对象的语言,C++在C的基础上最大的变化就是增加了类的概念。

17、C++中struct和class的区别

C++中struct和class差不多,主要的区别在于默认访问权限和默认继承权限不同,struct的默认访问权限和默认继承权限都是public,而class是private。

18、C++中重载和重写、重定义的区别

(1)、重载:重载指的是函数重载或运算符重载,指同一访问区内,被声明的几个参数列表不同的同名函数,C没有函数重载,C++能实现重载,主要是C++中对函数名的修饰和C不一样,C++对函数名的修饰,会把函数的参数类型加到函数名中,从而使得在程序中函数名一样,但在访问区中函数名不一样,返回值类型不能作为函数重载的依据。(属于静态多态)

(2)、重写:主要指派生类中重新定义父类中的出函数体外其他都完全相同的虚函数,重写的一定是虚函数,在子类中重写函数,其访问权限可以随便由程序员自己定义。(属于动态多态)

(3)、重定义:重定义,指在派生类中,重新定义和父类名字相同的非virtual函数,其参数列表和返回值都可以不同。则父类中的同名函数被子类所隐藏,如果想要调用父类中的同名函数,则需要加上父类的作用域。

19、类中的所有构造函数

作用:用于初始化类成员变量,在对象创建之初自动调用。可以重载构造函数;可以有参

(1)、无参构造:即默认构造函数,在程序员没有自定义构造函数的时候,编译器给自动生成的构造函数,函数体为空,不做任何操作。

(2)、有参构造:即程序员自定义的构造函数,用来对成员变量初始化。程序员自定义构造函数后,编译器将不会自动生成默认构造函数。

(3)、拷贝构造函数:拷贝构造函数的函数参数为对象本身的一个引用,用于根据已存在的对象复制出一个新对象,一般函数中,会对成员变量一一做等号复制到新的对象中。(完全的值复制)属于浅拷贝。当类中有指针类型的成员时,如果再做浅拷贝,那么就会存在内存重复释放的问题,解决办法,就是自己写一个深拷贝,对于指针类型的变量,在拷贝函数中,重新开辟一个新的堆空间,用于存放指针所指向的值。

20、类中的析构函数

作用:用于对类做收尾工作,在对象销毁时,自动调用。只能有一个析构函数;无参

(1)、默认析构函数:当程序员不自定义析构函数时,其编译器会自动生成一个析构函数,没有任何操作。

(2)、当程序员自定义析构函数后,编译器不会再生成默认的析构函数;析构函数常常被我们用来释放成员变量中的指针变量所指向的内存。

21、编译器提供的默认函数

(1)、编译器提供默认构造函数、拷贝构造函数、析构函数;

(2)、当程序员自定义构造函数后,编译器只提供拷贝构造函数、析构函数;

(3)、当程序员自定义拷贝构造函数后,编译器只提供析构函数。

21.2、类中构造函数调用的顺序,和析构的顺序

当一个类D,有虚继承A,正常继承B,然后类中有类对象成员C时:

先虚基类,再基类,再类成员函数,再本类

(1)、构造顺序 A->B->C->D

(2)、析构顺序D->C->B->A

(3)、当有多个类成员对象时,其构造函数调用的顺序为类对象声明的顺序。

22、C++面向对象的三大特性

封装、继承、多态

(1)、封装:对客观事物封装成抽象的类,并且可以自定义成员变量,和操作这些成员变量的方法,同时可以对这些成员变量和方法加以不同的权限,控制类对象和外界对象对其的访问。

(2)、继承:指某个对象可以获取另一个对象的属性和方法,可以无需重新编译源对象属性和方法,同时加以使用。通过继承所创建的类被称为“子类”或“派生类”,而被继承的类叫做“基类”或“父类”。

(3)、多态:多态分为静态多态和动态多态,静态多态主要是指函数重载或运算符重载,而动态多态主要指子类重写父类中的函数。作用:可以大大的提高程序的复用性,同时可以提高代码的扩充性和可维护性。

(3.1)、两者的区别就是函数地址是早绑定还是晚绑定,如果函数的调用是在编译器编译期间就确定了函数的调用地址,并产生代码,那么就属于函数地址早绑定,属于静态多态;如果函数调用的地址不能在编译期间确定,而是在运行时才确定的,那么就属于函数地址晚绑定,属于动态多态。

23、类的继承权限问题

当子类继承父类时,父类中的私有权限也会被子类所继承,但子类访问不到父类私有权限的属性和方法,所以,父类被子类继承的私有权限的属性和方法不属于子类的任何一个权限。

(1)、public继承:当子类以public继承时,其父类中的访问权限全都不变的继承到子类;

(2)、protected继承:当子类以protected继承时,其父类中的public被继承为protected;

(2)、private继承:当子类以private继承时,其父类中的public和protected都被继承为private。

23.1、菱形继承问题:

(1)、存在的问题:在子类中存基类二义性(子类的多个父类都继承于同一个祖父类,所以每个父类中都有一套祖父类的属性和方法,当子类在使用父类继承祖父类的方法时,就不清楚使用的是那个父类继承的方法,故存在二义性)。存在资源浪费(因为子类只需要有一套祖父类的属性和方法就可以了,但是子类从多个父类中,继承了多套一样的属性和方法,造成了内存的浪费)。

(2)、解决方法:利用虚继承解决,子类继承父类时,利用虚继承,即加关键字virtual。即子类继承的是虚基类指针,指向的是一份虚基类表。(对于菱形继承,那么子类将会产生三个虚基类指针,一份指向祖父类,另外两份指向父类)

24、C++中<>和" "引入头文件的区别

(1)、区别:<>引用的是系统文件,而" "引用的是自定义文件

(2)、查找路径不同:<>查找路径是编译器设置的头文件路径->系统变量

                                   而" "的查找路径是当前头文件目录->编译器设置的头文件路径->系统变量

25、虚函数的作用:(virtual关键字修饰)

(1)、实现动态多态;

(2)、可以让成员函数操作一般化,用基类的指针制指向不同的派生类对象时,基类指针调用其虚成员函数时,则会调用真正指向对象的成员函数,而不是基类中定义的虚成员函数。如果不是虚函数,那么不管基类指针指向那个派生类对象,调用时,调用的都是基类中定义的那个成员函数。

(3)、虚函数是非静态成员函数,所以不可和static一起修饰。

(4)、本质是覆盖,而不是重载声明。

26、虚函数和虚函数表

(1)、当一个类中定义有虚函数时,其该类将生成一个虚函数表,和一个虚函数指针。虚函数表中存放的是虚函数的地址(顺序为声明的顺序),其最后的位置存放的是NULL(所以虚表的大小比虚函数多一个);(虚函数指针是从属于类的,所以虚函数指针的大小是计算在类上面的)

(2)、当一个类继承了定义有虚函数的父类时:        

(2.1)、如果没有重写父类中的虚函数,那么子类的虚函数指针则指向父类的虚函数表,子类不会生成虚函数表;

(2.2)、如果重写了父类的虚函数,那么子类也会生成一张虚函数表,子类的虚函数指针就指向这张表,子类中重写父类中的虚函数的地址将覆盖原来虚函数表中的对应的地址,没有重写的虚函数其地址将保持不变。

(2.3)、如果子类有增加虚函数,那么新增的虚函数地址将追加到虚函数表尾端。

(3)、一个类只有一张虚函数表,所以类的对象共享一张虚函数表。在编译期间确定虚函数表。

(4)、多继承时,子类中有多个虚函数表和多个虚函数指针。

27、那些函数不能是虚函数:

(1)、构造函数:构造函数的作用是初始化对象,而派生类必须知道基类函数干了什么才能对其重写,对于虚函数的调用,主要是通过虚函数指针,而在构造函数中,由于每个对象都有一个指向虚函数表的虚函数指针,而该指针的初始化就是在构造函数中进行的,所以构造函数不能为虚函数。

(2)、内联函数:内联函数是在编译阶段进行函数体的替换操作,而虚函数是在程序运行期间才对其类型确定的,所以内联函数不能为虚函数。

(3)、静态函数:静态函数不属于对象,而是属于整个类的,静态函数没有this指针,不能对其进行任何操作,所以静态函数设置成虚函数没有任何意义。

(4)、友元函数:友元函数不属于成员函数,不能被继承,故不能被设置成虚函数。

(5)、普通函数:普通函数不属于成员函数,也不具备继承关系,所以不能为虚函数。

28、析构函数为什么要写成虚函数

(1)、降低内存泄漏的可能,当子类中存在成员变量在堆区申请内存空间时,基类指针指向子类对象时,在对象使用完毕后准备销毁时,如果基类析构函数没有定义成虚函数,那么编译器根据指针类型就会认为当前对象为基类,从而调用基类的析构函数,而不调用子类的析构函数,从而造成内存泄漏。如果基类把析构函数定义为虚函数后,基类指针在调用析构函数时,就会调用子类的析构函数之后再调用基类的析构函数,从而成功释放内存。

29、纯虚函数和抽象类

(1)、当类中存在纯虚函数时,那么该类就被定义为抽象类,抽象类不可实例化对象,子类必须重写父类的纯虚函数,否则子类也为抽象类。

(2)、声明纯虚函数的目的是,为了让子类只继承父类的函数接口。

30、浅拷贝和深拷贝

当类出现赋值的情况时,类就会自动调用拷贝构造函数

(1)、浅拷贝:当类对象在赋值时,将自动调用拷贝构造函数,拷贝构造函数将会对类成员一一的做等号复制,当类中没有指针类型的成员变量时,浅拷贝可以实现。

(2)、深拷贝:当类中有指针类型的成员时,如果再做浅拷贝,那里两个类中的两个指针变量指向同一个地址,当两个类在销毁时,都会释放指针所指向的内存,那么就会存在同一块内存重复释放的问题,这时就需要我们自定义拷贝构造函数,我们需要在堆区重新申请一块内存,用来存放数据,这时在两个对象销毁时,就不会产生内存重复释放的问题。

31、调用拷贝构造函数的时机

(1)、当类对象以值传递的形式作为函数参数时

(2)、当类对象以值的形式作为返回值时

(3)、当类对象初始化另一个对象时

32、拷贝构造函数为什么要是引用传递,而不能是值传递

(1)、为了防止无限递归的调用。当一个对象以值的形式传递时,编译器会调用其拷贝构造函数生成一个一个副本,而在需要拷贝的对象会创建出一个临时的对象去接收这个副本,而临时创建的对象由于没有实例化,那么就需要实例化,故又需要通过拷贝构造函数进行实例化,以此无限递归下去。

(2)、模型:a(b), a会创建一个临时ex = b,而ex 没有实例化,那么就又需要ex(b),而ex又会临时创建一个ex2 = b ,ex2又没实例化,又需要ex2(b),以此类推,无限递归。

33、内存泄漏

(1)、定义:就是申请一块空间,使用完毕后,没有释放掉,其表现方式是程序运行的时间越长,占的内存越多,最终用尽全部内存,使得整个程序崩溃掉。实际上就是,申请一块内存后,没有任何一个指针去指向这块内存,那么就说这个块内存已经泄漏。

(2)、检测内存泄漏的方法:在Linux中,可以用swap检查,查看多次输入swap命令后,其交换器的内存是否有减少。也可以使用一些工具,如netstat、vmstat。

34、#define和inline的区别

(1)、#define:是关键字,在预处理阶段进行字符替换,没有数据类型的区别,也没有数据类型安全检测。

(2)、inline:是函数,在程序编译阶段进行替换,inline有类型的区别,也有类型安全检查。相比于普通函数,其执行效率高;缺点就是浪费内存,在函数调用的地方,都会生成一份拷贝,而普通函数在的调用是通过函数地址的调用,不会生成拷贝。对于复杂性较高的inline函数,编译器会将其退变为普通函数。(inline函数中不能有循环)

(3)、相比于#define,inline更安全。

35、#define和typedef的区别

(1)、作用时间不一样,#define是在预处理阶段进行文本替换,而typedef是在编译阶段替换

(2)、类型检查上不一样,#define没有类型检查,而tepydef有类型检查

(3)、功能上:#define只是单纯的文本替换,可以给类型取别名,也可以定义其他变量、函数等等,而typedef只能给类型取别名。

(4)、作用域不同:#define在整个程序中都能使用,而typedef只有在定义的语句块内使用。

36、交换两个变量的值

(1)、引入第三个变量

int a = 10;
int b = 20;
int c = a;
a = b;
b = c;

(2)、使用库函数

swap(a, b);

(3)、采用宏定义

#define swap(x, y) do {int temp = x; x = y; y = temp} while(0)
#define swap(x, y, temo) {(temp) = (x), (x) = (y), (y) = (temp)}

(4)、不引用第三个变量

int a = 10;
int b = 20;
a = a + b;
b = a - b;
a = a - b;

猜你喜欢

转载自blog.csdn.net/m0_63001277/article/details/126962530