C++基础总结&面试复习笔记(持续更)

本文详细探讨了C++的基础知识与面试常见问题,包括C与C++的区别、宏#define与const的区别、指针与引用的区别、重载、重写与重定义的原理,以及多态的实现和缺陷。此外,还涵盖了new/delete与malloc/free的对比、C++中的四种类型转换、static的作用、const的作用等核心概念。文章深入浅出,是学习和准备C++面试的宝贵资料。
摘要由CSDN通过智能技术生成

一、C和C++的特点&区别是什么?

(1)C语言特点:
1.作为一种面向过程的结构化语言,易于调试和维护;

2.表现能力和处理能力极强,可以直接访问内存的物理地址;

3.C语言实现了对硬件的编程操作,也适合于应用软件的开发;

4.C语言还具有效率高,可移植性强等特点。

(2)C++语言特点:

1.在C语言的基础上进行扩充和完善,使C++兼容了C语言的面向过程特点,又成为了一种面向对象的程序设计语言;

2.可以使用抽象数据类型进行基于对象的编程;

3.可以使用多继承、多态进行面向对象的编程;

4.可以担负起以模版为特征的泛型化编程。

区别:

1)C是面向过程的,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是在C语言的基础上开发的一种面向对象编程语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。应用广泛。

2)C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。

3)C++支持函数重载,C不支持函数重载

4)C++中有引用,C中不存在引用的概念

二、#define和const的区别

1)#define定义的常量没有类型,所给出的是一个立即数;const定义的常量有类型名字,存放在静态区域。

2)处理阶段不同,#define定义的宏变量在预处理时进行替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有一个拷贝。

3)#define定义的常量是不可以用指针去指向,const定义的常量可以用指针去指向该常量的地址。

4)#define可以定义简单的函数,const不可以定义函数。


三、指针和引用的区别

指针:对于一个类型T,T*就是指向T的指针类型,也即一个T*类型的变量能够保存一个T对象的地址。如:int* p;

引用:引用是一个对象的别名,主要用于函数参数和返回值类型,符号X&表示X类型的引用。如:int& x;

区别:

1)指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。

2)引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。

3)有多级指针,但是没有多级引用,只能有一级引用。

4)指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)

5)sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。

6)引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

7)引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const 指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针。

四、重载重写重定义的区别

   重载(overload):在同一个类中,函数名相同,参数列表不同,

     编译器会根据这些函数的不同参数列表,将同名的函数名称做修饰,从而生成一些不同名称的预处理函数,未体现多态。

   重写(override):也叫覆盖,子类重新定义父类中有相同名称相同参数的虚函数。

   主要是在继承关系中出现的,被重写的函数必须是virtual的,重写函数的访问修饰符可以不同,尽管virtual是private的,子类中重写函数改为public,protected也可以,体现了多态。

  重定义(redefining):也叫隐藏,子类重新定义父类中有相同名称的非虚函数,参数列表可以相同可以不同,会覆盖其父类的方法,未体现多态。

1.如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。

2.如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。(如果有virtual就成重写了)


五、多态(polymorphism)的原理理解

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。

C++的多态分为静态多态与动态多态。

静态多态就是重载,因为在编译期决议确定,所以称为静态多态。在编译时就可以确定函数地址。

动态多态就是通过继承重写基类的虚函数实现的多态,因为实在运行时决议确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。

C++多态性是通过虚函数来实现的,即在基类的成员函数前面加上virtual来实现,虚函数允许子类重新定义成员函数。 那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

1. 实现原理
1.当类中存在虚函数时,编译器会在类中自动生成一个虚函数表
2.虚函数表是一个存储类成员函数指针的数据结构
3.虚函数表由编译器自动生成和维护
4.virtual 修饰的成员函数会被编译器放入虚函数表中
5.存在虚函数时,编译器会为对象自动生成一个指向虚函数表的指针(通常称之为 vptr 指针)

多态缺陷

●降低了程序运行效率(多态需要去找虚表的地址) 
●空间浪费
 

另:虚函数的实现,虚函数表的理解。


每个包含了虚函数的类都包含一个虚表。 
我们知道,当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。

虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。 
虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了。
这篇文章对虚函数表讲的很清晰:感谢https://blog.csdn.net/lihao21/article/details/50688337

封装,继承,多态是面向对象设计的三个特征,而多态可以说是面向对象设计的关键。C++通过虚函数表,实现了虚函数与对象的动态绑定,从而构建了C++面向对象程序设计的基石。

注意思考问题:

1).为什么调用普通函数比调用虚函数的效率高?
因为普通函数是静态联编的,而调用虚函数是动态联编的。

联编的作用:程序调用函数,编译器决定使用哪个可执行代码块。

静态联编 :在编译的时候就确定了函数的地址,然后call就调用了。
动态联编 : 首先需要取到对象的首地址,然后再解引用取到虚函数表的首地址后,再加上偏移量才能找到要调的虚函数,然后call调用。
明显动态联编要比静态联编做的操作多,肯定就费时间。

2).为什么要用虚函数表(存函数指针的数组)?

实现多态,父类对象的指针指向父类对象调用的是父类的虚函数,指向子类调用的是子类的虚函数。
同一个类的多个对象的虚函数表是同一个,所以这样就可以节省空间,一个类自己的虚函数和继承的虚函数还有重写父类的虚函数都会存在自己的虚函数表。
3).为什么要把基类的析构函数定义为虚函数?

在用基类操作派生类时,为了防止执行基类的析构函数,不执行派生类的析构函数。因为这样的删除只能够删除基类对象, 而不能删除子类对象, 形成了删除一半形象, 会造成内存泄漏.

4).为什么子类和父类的函数名不一样,还可以构成重写呢?

因为编译器对析构函数的名字做了特殊处理,在内部函数名是一样的。

六、new/delete和malloc/free

1、申请的内存所在位置 
new是一种操作符,从自由存储区上为对象动态分配内存空间;而malloc是从内存池中提取一块合适的内存,即从堆上动态分配内存空间。 自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符申请的内存都在自由存储区。而堆操作系统的术语,是操作系统所维护的一块特殊内存,用于动态分配。自由存储区可以是堆也可以为静态存储区(取决于operate new实现方式)。 
2、返回类型 
new操作符内存分配成功时,返回是对象类型指针,类型严格与对象匹配,无需进行类型转换,故new是符合类型安全性操作符。 malloc内存分配成功则是返回void*,需要通过强制类型转换将void*转换成所需类型。 
3、内存分配失败时的返回值 
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL; malloc分配内存失败时返回NUll。 
4、是否需要制定内存大小 
使用new操作符申请内存分配时无需指定内存大小,编译器会根据类型信息自行计算,而malloc则需要指出内存大小。

5、 opeartor new /operator delete可以重载,而malloc不行。

6、C++中有new[]进行分配内存和delete[]释放内存来专门处理数组类型; new[]和delete[]必须配套使用,否则会出现内存泄漏,它们会分别调用构造函数和析构函数并初始化每一个数组元素。 而malloc如果要动态分配一个数组的内存,需要手动计给出合适的空间大小。

int char* a = new char[10];
int char* b = malloc(sizeof(char) * 10);

delete和delete[]的区别

delete只会调用一次析构函数,而delete[]会调用每个成员的析构函数

用new分配的内存用delete释放,用new[]分配的内存用delete[]释放

七、C++中的四种类型转换

1. dynamic_cast(表达式):只用于对象、指针和引用,主要用于执行安全的向下转型。当用于多态类型时,它允许任意的隐式类型转换及相反的过程,但在反向转型时会检查操作是否有效,这种检测在运行时进行,若检测不是有效的对象指针,返回NULL;

2. static_cast(表达式):允许执行任意的隐式转换和相反的动作。应用在类指针上,父类指针和子类指针可以相互转换,转换过程中不检查。但是不能将const转化为非const;

3. const_cast(表达式):用来强制设置对象的常量性或移除;

reinterpret_cast(表达式):可转换一种类型的指针为其它类型的指针,也可以将指针转换为其它类型。非相关类型间的转换,不对操作内容进行检查。


八、static的作用

static是c/c++的关键词

static在C中的作用
1 static修饰局部变量
static修饰局部变量时,使得被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。(无论是局部静态还是全局静态)

局部静态变量使得该变量在退出函数后,不会被销毁,因此再次调用该函数时,该变量的值与上次退出函数时值相同。值得注意的是,生命周期并不代表其可以一直被访问,因为变量的访问还受到其作用域的限制。

注:static变量的一个性质:初始化只有一次,但是可以多次赋值。

2 static修饰全局变量

全局变量本来就存储在静态区,因此static并不能改变其存储位置。但是,static限制了其链接属性。被static修饰的全局变量只能被该包含该定义的文件访问。

3 static修饰函数

static修饰函数使得函数只能在包含该函数定义的文件中被调用。

(感谢https://blog.csdn.net/petersmart123/article/details/52372754

static在C++中的作用

在C++中static不光具备C中所有的作用,而且对于静态成员变量和静态成员函数。所有的对象都只维持同一个实例。 
因此,采用static可以实现不同对象之间数据共享。

在头文件b.h中声明静态函数fun(),在文件b.cpp中定义静态函数fun(),编译器会报错,指明b.h中的fun函数未定义。 
对于静态函数,声明和定义需要放在同一个文件夹中。

如果将static函数定义在头文件中,则每一个包含该头文件的文件都实现了一个fun函数。因此static实现了不同文件中定义同名的函数,而不发生冲突。

在多人协同工作的项目中,为了避免出现同名的函数冲突,可以将函数定义为static,从而避免冲突的发生。

九、const的作用

const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。const代表只读/不可修改,而不仅仅是常量。

(想修改可在变量前面加volatile)。

const的用法大致可分为以下几个方面:

(1)const修饰基本数据类型

const修饰的变量,必须初始化。

const修饰全局/局部变量,被修饰的全局变量不允许被修改。

(2)const应用到函数中

1.const修饰函数参数

 a.传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参)

void function(const int Var);

b.参数指针所指内容为常量不可变

void function(const char* Var); //指向整型常量的指针。

c.它不能在指向别的变量,但指向(变量)的值可以修改

void function(char* const Var);//指向整型的常量指针

d.参数为引用,为了增加效率同时防止修改。修饰引用参数时:

void function(const Class& Var); //引用参数在函数内不可以改变

void function(const TYPE& Var); //引用参数在函数内为常量不可变

2const 修饰函数返回值
    const修饰函数返回值其实用的并不是很多,它的含义和const修饰普通变量以及指针的含义基本相同。
    a.const int fun1() //这个其实无意义,因为参数返回本身就是赋值。
    b. const int * fun2() //调用时 const int *pValue = fun2(); 
                          //我们可以把fun2()看作成一个变量,即指针内容不可变。
    c.int* const fun3()   //调用时 int * const pValue = fun2(); 
                          //我们可以把fun2()看作成一个变量,即指针本身不可变。

一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。

(3)const在类中的用法

1const修饰成员变量
const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

2const修饰成员函数

const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。一般写在函数的最后来修饰。

a. const成员函数不被允许修改它所在对象的任何一个数据成员(mutable修饰的变量除外)。

b. const成员函数能够访问对象的const成员,而其他成员函数不可以。

 3const修饰类对象/对象指针/对象引用

const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。

const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。

注:将Const类型转化为非Const类型的方法

采用const_cast 进行转换。 
用法:const_cast <type_id>  (expression) 
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

·             常量指针被转化成非常量指针,并且仍然指向原来的对象;

·             常量引用被转换成非常量引用,并且仍然指向原来的对象;

·             常量对象被转换成非常量对象。

使用const的一些建议:

·            1.要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;

·             2.要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;

·             3.在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;

·             4.const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;

·             5.不要轻易的将函数的返回值类型定为const;

·             6.除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;

·             7.任何不会修改数据成员的函数都应该声明为const 类型。

十、C++文件编译与执行的四个阶段

1)预处理:根据文件中的预处理指令来修改源文件的内容

2)编译:编译成汇编代码

3)汇编:把汇编代码翻译成目标机器指令

4)链接:链接目标代码生成可执行程序

十一、memset和strcpy和memcpy的区别(涉及手撕代码)

strcpy

原型:extern char *strcpy(char *dest,char *src);
用法:#include <string.h>
功能:把src所指由NULL结束的字符串复制到dest所指的数组中。
说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
    返回指向dest的指针。

例:char a[100],b[50];strcpy(a,b);如用strcpy(b,a),要注意a中的字符串长度(第一个‘\0’之前)是否超过50位,如超过,则会造成b的内存地址溢出。

memcpy
原型:extern void *memcpy(void *dest, void *src, unsigned int count);
用法:#include <string.h>
功能:由src所指内存区域复制count个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。可以拿它拷贝任何数据类型的对象。

举例:char a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a),会造成b的内存地址溢出。

memset
原型:extern void *memset(void *buffer, int c, int count);
用法:#include <string.h>
功能:把buffer所指内存区域的前count个字节设置成字符c。
说明:返回指向buffer的指针。用来对一段内存空间全部设置为某个字符。

举例:char a[100];memset(a, '\0', sizeof(a));

memset可以方便的清空一个结构类型的变量或数组。

十二、构造函数,拷贝构造函数和赋值函数的区别和实现

首先说一下一个C++的空类,编译器会加入哪些默认的成员函数

1.·默认构造函数

2.·析构函数

3·赋值函数(赋值运算符)   重载的格式:返回值为该类类型的引用,通过operator关键字后加上要重载的符号完成,它视情况也有参数

 String& operator=(String s)
    {
        std::swap(_pStr, s._pStr);
        return *this;
    }

4. 取址(&)运算符重载     

取址操作符重载函数返回值为该类型的指针,无参数。

String* operator&()
{
    return this;
}

5. const修饰的取址运算符重载        (在5函数前面加const)

6.拷贝构造函数

1)构造函数一般被调用是对类的数据成员进行初始化和分配内存。(构造函数的命名必须和类名完全相同)。在单个参数时,函数前需要加explicit,防止隐式转换。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值