【C++】简答题

1:指针(*)、引用(&)、解引用(*)、取地址(&)的概念和区别

概念:

指针指向一块内存,指针保存的内存的地址;

引用是变量的别名,本质是引用该变量的地址。

解引用是取指针指向的地址的内容,取地址是获取变量在内存中的地址。

区别:

引用使用无需解引用,指针需要解引用。

引用不能为空,指针可以为空。

引用在定义时被初始化一次,之后不可变;指针指向的值和本身的值是可变的,也就是说指针只是一块地址,地址里的东西可变。

程序会给指针变量分配内存区域,而引用不需要分配内存区域。

2:memset、memcpy和strcpy的区别

memcpy是内存拷贝函数,可以拷贝任何数据类型的对象,例如memcpy(b,a,sizeof(b))。

strcpy只能拷贝字符串,遇到'\0'结束拷贝。

memset用来对一段内存空间全部设置为某个字符

3:struct和class的区别,struct与union的区别

struct和class都是声明类的关键字

union:

不同于结构成员——它们在结构中都具有单独的内存位置,联合成员则共享同一个内存位置。也就是说,联合中的所有成员都是从相同的内存地址开始。因此,可以定义一个拥有许多成员的联合,但是同一时刻只能有一个成员允许含有一个值。联合让程序员可以方便地通过不同方式使用同一个内存位置。

一个union 只配置一个足够大的空间以来容纳最大长度的数据成员。

在C++里,union 的成员默认属性页为public。union 主要用来压缩空间。如果一些数据不可能在同一时间同时被用到,则可以使用union。

区别是(struct/class):

在默认情况下,struct的成员变量是公用的;

在默认情况下,class的成员变量是私有的;

struct保证成员按照声明顺序在内部中存储,class不能保证。

对于继承来说,struct默认公有继承,class默认私有继承。

区别是(struct / class&union):

一个union类型的变量,所有成员变量共享一块内存,该内存的大小有这些成员变量中长度最大的一个来决定,struct中成员变量内存都是独立的。

union分配的内存是连续的,而struct不能保证。

区别是(struct/union):

struct与union都是由多个不同的数据类型成员组成。

union共享一块存储地址空间,而struct不是。

对于union的一个成员赋值,其它成员会重写,但struct不会。

4:指针在16位机、32位机、64位机分别占用了多少个字节

16位机——2字节

32位机——4字节

64位机——8字节

5:如何引用一个已经定义过的全局变量

如果在同一文件下,直接引用。

如果不在同一文件下,直接引用头文件,或者使用extern引用响应的变量。

extern:

引用同一个文件中的变量;

引用另一个文件中的变量;

引用另一个文件中的函数;

extern关键字只需要指明类型和变量名就行了,不能再重新赋值。

6:全局变量可不可以定义在可被多个.c文件包含的头文件中

因为全局变量的作用域是整个源程序,可以声明多次,但只能被定义一次。

7:对于一个频率使用短小的函数,在C语言中应用什么实现,在C++中应用什么实现

C语言常用宏定义,C++常用inline。

8:main函数执行前,会执行什么代码

全局对象的构造函数会在main函数之前执行。

9:局部变量能否和全局变量重名

可以,但是在局部内,局部变量会屏蔽全局。要用全局变量,需要使用作用域作用符::

10:描述内存分配方式以及他们的区别

静态储存区域分配。该区域在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

上创建。在执行函数时,函数的局部变量存储在该区域,函数执行结束时会释放该存储空间。

上分配。也成为动态内存分配。程序在运行的时候用malloc或new申请内存,程序员自己负责free或delete释放内存。动态内存的生存期由程序员决定,使用灵活。

11:类的成员函数重载、覆盖和隐藏的概念和区别

概念:

函数重载,在同一个作用域内,有几个同名的函数,但是参数列表的个数和类型不同。

函数覆盖,派生类函数覆盖基类函数,函数名、参数类型、返回值类型一模一样。派生类对象会调用子类中覆盖版本,覆盖父类中的函数版本。

函数隐藏,是指派生类的函数屏蔽了与其同名的基类函数。

区别(覆盖/重载):

函数是否处于不同的作用域,参数列表是否一样,基类函数是否有virtual关键字。

区别(覆盖/隐藏):

派生类的函数与基类的函数同名,但是参数不同。此时,无论有无virtual关键字,基类的函数将被隐藏。

派生类的函数与基类的函数同名,参数也相同,但是基类函数没有virtual关键字。此时,基类的函数会有隐藏virtual,就是覆盖。

如果子类覆盖父类的函数,但是不加virtual,也能实现多态,由于virtual修饰符会被隐藏继承,但是尽量加上。

12:static关键字

为什么引入static:

编译器为局部变量在栈上分配空间,但是函数执行结束时会释放掉。所以static可以解决函数执行完后变量值被释放的问题,static变量存储在静态储存区。

缺点:不安全,因为作用域是全局的。

优点:因为静态变量对所有对象所公有的,可以节省内存,提高时间效率。

静态数据成员的作用:

可以用于全局变量的定义,分配静态存储区,程序运行结束前不会被释放。

声明和定义静态成员函数,表示该函数为静态,之能在本文件中被调用。

定义静态局部变量,只能被初始化一次,程序运行结束后才会释放。区别是作用域的范围。

13:const与#define的概念和优缺点

const用来定义常量、修饰函数参数、修饰函数返回值,可以避免被修改,提高程序的健壮性。

define是宏定义,在预处理阶段会进行替换。

区别:

const定义数据有数据类型,而宏常量没有数据类型。

编译器可以对const常量进行类型检查。

宏定义只进行字符替换,没有类型安全检查,所以字符替换时可能会出错。

static关键字的作用:

函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,下次调用时仍维持上次的值;

在模块内的static全局变量可以被模块内所用函数访问,但是不能被模块外的其它函数访问;

在模块内的static函数只可能被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

在类中的static成员变量属于整个类所拥有,对类的所有对象只拷贝一份;

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

const关键字的作用:

欲阻止一个变量被改变,可以使用const关键字。

定义const变量时,通常需要对它进行初始化,否则值将无法改变。

对指针来说,可以指定本身为const,也可以指定指针所指的数据为const,或者二者同时指定为const。

在一个函数声明中,const可以修饰形参,表明是一个输入参数,在函数内部不能改变其值。

对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量。

对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”(防止错误)。

14:堆栈溢出的原因

没有回收垃圾资源。

栈溢出:

一般都是由越界访问导致的。例如局部变量数组越界访问或者函数内局部变量使用过多,超出了操作系统为该进程分配的栈的大小。

堆溢出:

由于堆是用户申请的,所以溢出的原因可能是程序员申请了资源但是忘记了释放。

15:刷新缓冲区方式⭐

缓冲区:

缓冲区是内存空间的一部分,也就是说在内存空间中预留了一定大小的存储空间,这些存储空间用来缓冲输入或者输出的数据,这部分预留的空间就叫做缓冲区,根据其对应的输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

当调用输入函数scanf()时,输入函数会将我们输入的数字,输入到缓冲区,而当我们的输入缓冲区有内容时,再次输入将不会被执行,而是直接跳过执行,将输入缓冲区的内容赋给变量。然后再将新内容放入缓冲区。

程序正常结束, 作为main 返回工作的一部分,将情况所有输出缓冲区;

一些不确定的时候,缓冲区可能已满;

使用manipulator显示属性缓冲区,比较常用的endl,flush,ends:

cout<<"hi"<<endl; //插入换行,同时刷新输出缓冲区
cout<<"hi"<<ends; //末尾插入null,刷新缓冲区
cout<<"hi"<<flush;//刷新缓存区,不添加任何数据

使用unitbuf设置流的内部状态,适合所有输出都要刷新缓冲区,unitbuf 和nounitbuf 之间的区域每次写完后都刷新流。

cout<<unitbuf<<"first"<<"second"<<nounitbuf;

等价于

cout<<"first"<<flush<<"second"<<flush;

使用tie将输入流和输出流关联起来,这种情况下,在读输入流时将刷新其关联的输出缓冲区,标准库默认将cout和cin绑在一起。

cin.tie(&cout);//cin和cout关联

cin.tie(0); //解除该流上已存在的捆绑

16:类和对象两个基本概念

类——用来描述一组具有相似属性的东西的对象的一种数据结构

类中有数据成员的声明和定义,有成员函数的实现代码。

对象就是类的实例化。要想使用类,就只能进行实例化。

17:介绍一下STL,详细说明STL如何实现vector

STL是标准模板库,由容器算法迭代器组成。

STL有以下的一些优点:

  • 可以便捷的排序 sort()
  • 调试程序时更加安全方便。
  • STL是跨平台的,再Linux下也能使用。

vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间。

容器是一种类 类型。用来存储数据。

STL主要用7种容器:vector,list,deque,map,multimap,set,multiset

18:变量的声明和定义有什么区别

变量的声明是告诉编译器我有某个类型的变量,但不会为其分配内存。

但是定义会为其分配内存。

19:简述#define #endif 和#ifndef的作用

这三个命令一般是为了避免头文件被重复引用。

#ifndef CH_H //意思是如果没有引用ch.h
#define CH_H //引用ch.h
#endif       //否则不需要引用

20:C++继承机制

public:类本身、派生类(子类)和其它类均可访问;

protected:类本身和派生类均可访问,其它类不能访问;

private:类本身可访问,派生类和其他类不能访问。

private+(public/protected/private) => 不可访问

(public/protected)+public => public/protected

(public/protected)+protected => protected

(public/protected)+private(默认) => private

21:什么是内存泄漏,C++内存泄漏检测

内存泄漏是指程序中动态分配了内存,但是在程序结束时没有释放这部分内存,从而造成那一部分内存不可用的情况。

有一些内存泄漏的检测工具,如BoundsChecker。

静态内存泄漏通过工具或者仔细检查代码找到泄漏点。

动态内存泄漏很难检查,一般通过在代码种加断点跟踪和Run-Time内存检测工具来查找。

内存泄漏的检测可以分为以下几个步骤:

  • 看代码new之后是否delete,就是申请了静态内存用完是否释放。看析构函数是否真的执行,如果没有真正执行,就需要动态释放对象。
  • 让程序长时间运行,看任务管理器对应的内存是不是一直向上增加。
  • 使用常用内存泄漏检测工具来检测内存泄漏点。

22:头文件的作用是什么

头文件用于保护程序的声明

通过头文件可以来调用库函数。因为有些代码不能向用户公布,只要向用户提供头文件和二进制的库即可。

如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会报错,这一规则能大大减轻程序员调试、改错的负担。

23:函数模板与类模板有什么区别⭐

函数模板的实例化是由编译程序在处理函数调用时自动完成的;

类模板的实例化必须由程序员在程序中显式地指定

template<Type>
Type min(Type a,Type b){
	return a<b?a:b;
}

参数一般分为类型参数和非类型参数。

类型参数代表了一种具体的类型

非类型参数代表了一个常量表达式

24:system("pause")的作用

调用DOS的命令,按任意键继续,和getchar()差不多;省去了使用getchar();

区别是一个属于系统命令,一个属于C++标准函数库。

25:析构函数和虚函数的用法和作用

析构函数是类的成员函数,在类对象生命周期结束的时候,由系统自动调用,释放在构造函数中分配的资源。

虚函数是为了实现多态。含有纯虚函数的类称为抽象类,不能实例化对象,主要用作接口类。

析构函数的特点:

函数名称固定;没有返回类型,没有参数;不可以重载,一般由系统自动调用。

26:new、delete;molloc、free关系

new和delete是一组,用调用构造函数来实例化对象和调用析构函数释放对象申请的资源

molloc和free是一对,用来申请内存和释放内存,但是申请和释放的对象只能是内部数据类型(非自定义)。

区别:

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。

malloc/free只能操作内部数据类型。

27:delete与delete[]的区别

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

delete与new配套,delete[]与new[]配套,用new分配的内存,用delete删除,另一个同理。

28:继承的优缺点

优点:

继承可以方便地改变父类的实现,可以实现多态,子类可以继承父类的方法和属性。

缺点:

破坏封装,子类和父类可能存在耦合。

子类不能改变父类的接口。

29:C和C++有什么不同

C是面向过程,更偏向逻辑设计;

C++是面向对象,提供了类,偏向类的设计。

C适合要求代码体积小的,效率高的场合,比如嵌入式。

C++引入new/delete运算符,取代了C中的malloc/free库函数。

C++引入了引用的概念,类的概念,函数重载的特性。

30:析构函数的调用次序,子类析构时调用父类的析构函数吗(是)

先派生类的析构,再基类的析构。

在基类的析构调用时,派生类的信息已经被全部销毁。

定义一个对象时先调用基类的构造函数,在调用派生类的构造函数。

31:什么是“野指针”

野指针是指向一个已经被删除的对象或者无意义地址的指针。

与空指针不同,野指针不能通过是否为NULL来避免,只能通过养成良好的编程习惯来避免。

形成野指针的主要原因是:

指针变量没初始化;指针p被free或者delete之后,没置为NULL

32:常量指针和指针常量的区别

常量指针(指向内容不能变):是一个指向常量的指针。可以防止对指针误操作而修改该常量。

指针常量(指向内容可修改):是一个常量,而且是一个指针。指针常量不能修改指针所指向的地址,一旦初始化,地址就固定了,不能对它进行移动操作。但是指针常量的内容是可变的。

33:C++文件编译与执行的四个阶段

第一阶段:预处理阶段。根据文件中的预处理指令来修改源文件的内容。如#include指令,作用是把头文件的内容添加到.cpp文件中。

第二阶段:编译阶段。将其翻译成等价的中间代码或汇编代码。

第三阶段:汇编阶段。把汇编语言翻译成目标机器指令。

第四阶段:是链接,例如,某个源文件中的函数可能引用了另一个源文件中定义的某个函数,在程序中可能调用了某个库文件的函数。

34:面向对象的三大特性是哪些

封装:将客观事务封装成抽象的类,而类可以把自己的数据和方法暴露给可信的类或者对象,对不可信的类或者对象则进行信息隐蔽。封装使代码模块化,目的是为了代码重用。

继承:可以使用现有类的所有功能,而且无需重新编写原来的类即可对功能进行拓展。

多态:一个类实例的相同方法在不同情景下有不同的表现形式,使不同内部结构的对象可以共享相同的外部接口。允许将子类类型的指针赋值给父类类型的指针。

35:什么是预编译?何时需要预编译?

预编译又称为预处理,是做些代码文本的替换工作。

处理#开头的指令,如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等。

36:内联函数与宏有什么区别

内联函数在编译时展开,宏在预编译时展开。

在编译的时候内联函数可以直接被嵌入到目标代码中,而宏只是一个简单的文本替换。

内联函数可以完成诸如类型检测语句是否正确等编译功能,宏就不具备这样的功能。

内联函数是函数,宏不是函数。

37:iostream与iostream.h的区别

#include<iostream> 标准输出流,要用到命名空间。

#include<iostream.h> 非标准输出流

38:namespace的使用

因为标准库非常的庞大,所以程序员在选择的类的名称或者函数名时就很有可能和标准库的某个名字相同。所以为了避免冲突,把标准库中的一切都放在命名空间std中。

C++标准库中的所有标识符都被定义于一个名为std的namspace中。

39:堆与栈的区别

堆是静态的,由用户申请和释放;栈是动态的,保存程序的局部变量。

申请后系统的响应不同。

栈:只要栈的剩余空间大于申请空间,系统就为程序提供内存,否则将抛出栈溢出异常。

堆:当系统收到程序申请时,先遍历操作系统中记录空闲内存地址的链表,寻找第一个大于所申请空间的堆结点,然后将该结点从空间结点链表中删除,并将该结点的空间分配给程序。

申请大小限制不同。

栈:在windows下,栈的大小一般是2M,如果申请的空间超过栈剩余的空间时,将提示overflow。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

40:含参数的宏与函数的优缺点

优点:在预处理阶段完成,不占用编译时间,同时,省去了函数调用的开销,运行效率高。

缺点:不会进行类型检查,多次宏替换会导致代码体积变大,而且由于宏本质上是字符串替换,故可能会由于一些参数的副作用导致得出错误的结果。

函数

优点:有类型检查,比较安全。

缺点:函数调用需要参数,返回地址等的入栈出栈开销,效率没有带参数的宏高。

41:多态的作用

可以隐藏实现的细节,使得代码模块化;方便扩展代码;可以实现接口重用。

42:C++纯虚函数,虚函数,虚函数的实现,什么是虚指针

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但是要求任何派生类都要定义自己的实现方法。

virtual void f()=0是一个接口,子类必须实现这个接口虚指针是虚函数的实现细节。

有虚函数的每一个对象都有一个虚指针指向该类的虚函数表。

虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖。

纯虚函数和虚函数的区别为,纯虚函数子类必须实现。

纯虚函数的优点

  • 可以实现多态特性
  • 定义一个标准的接口,在派生类中必须予以重写实现多态性。

抽象类包含纯虚函数的类型称为抽象类。由于抽象类中包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

多态性可分为静态多态和动态多态。函数重载和运算符重载属于静态多态,动态多态性是通过虚函数来实现的。

为什么基类析构函数是虚函数

编译器总是根据类型来调用类成员函数。

但是一个派生类的指针可以安全地转化为一个基类的指针。

这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类对象,调用的都是基类的析构函数而不是派生类的。如果依赖派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源的泄漏。

为什么构造函数不能为虚函数

虚函数采用一种虚调用的方法。虚调用是一种可以在只有部分信息的情况下的工作的机制。如果创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。

如果虚函数是有效的,那为什么不能把所以的函数设置为虚函数

不行。因为每个虚函数的对象都要维护一个虚函数表,因此在使用虚函数的时候都会产生一定的系统开销,这是没有必要的。

43:引用和多态的关系

引用就是对象的别名。引用主要用作函数的形参。

引用必须用与该引用同类型的对象初始化。

引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类引用可以指向它的派生类实例。

int ival=1024;
int &refval=ival;
//const对象的引用只能是const类型的
const int ival=1024;
const int &refval=ival;

多态是通过虚函数实现的。

44:指针和引用有什么区别?为什么传引用比传指针更安全?如果我使用常量指针难道不行吗?

引用在创建时必须初始化,保证引用的对象是有效的,所以不存在NULL引用;而指针在定义的时候不必初始化,所以,指针则可以是NULL,可以在定义后面的任何地方重新赋值。所以引用更安全。

引用一旦被初始化为指向一个对象,它就不能改变为引用另一个对象;指针在任何时候都可以改变为指向另一个对象。

引用的创建和销毁并不会调用类的拷贝构造函数。

const指针(常量指针)仍然存在空指针,可能会产生野指针,所以不安全

45:拷贝构造函数相关问题,深拷贝,浅拷贝,临时对象等

深拷贝意味着拷贝了资源和指针。

浅拷贝意味只拷贝了指针,没有拷贝资源。这样使得两个指针指向同一份资源,可能造成对同一份资源析构两次,程序崩溃。而且浪费时间,不安全。

临时对象的开销比局部对象小一些。

常见临时对象创建情况:以值的方式给函数传参;类型转换;函数需要返回对象时。

46:构造函数的特点

构造函数只在建立对象的时候被自动调用一次

构造函数必须是公共的,否则无法生成对象

构造函数只负责为自己的类构造对象

47:面向对象如何实现数据隐藏

定义类来实现数据隐藏。

48:字符指针、浮点数指针、函数指针哪种变量占用的内存最大

所有指针变量占用内存单元的数量都是相同的。

49:什么是模板和宏,模板怎么实现,模板的优缺点,模板特化的概念,为什么特化

标准库中大量采用了模板技术,比如容器。

模板是一个蓝图,它本身不是类或者函数。编译器用模板产生指定的类或者函数的特定类型的版本。版本的形参分别为类型形参非类型形参。类型形参就是表示类型的形参,跟在关键字typename后面;非类型实参用来表示常量表达式。

50:空指针和悬垂指针的区别

空指针是指被赋值为NULL的指针;

删除(delete)指向动态分配对象的指针将会产生悬垂指针

空指针可以被多次删除(delete),而悬挂指针再次删除时程序会变得非常不稳定。

使用空指针和悬垂指针都是非法的,有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但是和悬垂指针相比是一种可预料的崩溃。

51:指针数组和数组指针,函数指针和指针函数的相关概念

指针数组:用于存储指针的数组。int *a[i];

数组指针:指向数组的指针。int (*a)[4];

指针函数:函数返回类型是某一类型的指针 int *f(x,y);

函数指针:是指向函数的指针变量,本质上是一个指针。int (*f)(int x) 类型说明符 (*指针的变量名)(参数)

指针函数与函数指针表示方式不同。辨别方式为看函数名前面的指针*号有没有被括号包含,如果包含是函数指针,反之是指针函数。

52:什么是智能指针

当类中有指针成员时,一般有两种方式来管理指针成员

(1)每个类对象都保留一份指针指向的对象的拷贝。

(2)使用智能指针,从而实现指针指向的对象的共享。实质是使用计数器与对象相关联,这样做可以确保对象正确的删除,避免悬垂指针。

每次创建类的新对象时,初始化指针并将引用计数置为1;

当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之响应的引用计数;

对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数,并增加右操作数所指对象的引用计数;

调用析构函数时,构造函数减少引用计数。

53:C++空类默认有哪些成员函数

默认构造函数、析构函数、复制构造函数、赋值函数。

54:哪一种成员变量可以在一个类的实例之间共享

static静态成员变量

55:什么是多态,多态有什么作用,如何实现,多态的缺点

多态就是一个接口,多种方法。

所以说,多态的目的是为了实现接口重用

也就是说,不论传递过来的究竟哪个类的对象,函数都能通过同一个接口调用到适应各自对象的实现方法

C++的多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者重写

重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同参数类型不同。编译器会根据函数列表的不同,生成一些不同名称的预处理函数,来实现同名函数的重载。但这并没有体现多态性。

如果函数调用的地址在编译器编译期间确定,并生产代码,则是静态的。

如果函数调用的地址在运行时确定,就是动态的。

多态实现

最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象调用相应的虚函数,可以根据指向的子类的不同实现不同的方法

多态缺点

如果没有虚函数,就没有利用C++多态性。

利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写的函数

56:虚函数表解析和内存布局

虚函数是通过一张虚函数表来实现的。就像地图一样,指明了实际所应该调用的函数的地址。

C++的编译器保证了虚函数表的指针存在于对象实例中的最前面的位置(为了性能)。

因此我们可以通过对象实例的地址得到这张虚函数表,然后通过遍历其中的函数指针,并调用相应的函数。

为什么可以由父类的指针调用子类的对象的虚函数

Derive d;//Derive是Base的子类
Base *b1 = &d;
b1->f();//Derive::f();

57:有哪几种情况只能用构造函数初始化列表而不能用赋值初始化

const成员,引用成员。

58:C++如何阻止一个类被实例化,一般在什么时候将构造函数声明为private

  • 将类定义为抽象基类或者将构造函数声明为private;

  • 不允许类外部创建类对象,只能在类内创建对象

59:类使用static成员的优点,如何访问

static成员的名字是在类的作用域中,因此可以避免与其它类的成员或全局对象名字冲突

可以实施封装。static成员可以是私有成员,而全局对象不可以;

static成员是与特定类关联的,可以清晰地显示程序员的意图

60:static数据成员和static成员函数

static数据成员

static数据成员独立于该类的任意对象而存在;

static数据成员(const static数据成员外)在类定义体内声明,必须在类外初始化

不像普通数据成员,static成员不能再类的定义体中初始化,只有在定义时才初始化

static数据成员定义放在cpp文件中,不能放在初始化列表中。

const static成员可以就地初始化。

tip变量的声明和定义

声明是告诉编译器变量的名称和类型,而不分配内存。

定义是为了给变量分配内存,可以为变量赋初值。

static成员函数

类的外部定义,static成员函数没有this形参,它可以直接访问所属类的static成员不能直接使用非static成员。因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。同时,static成员函数也不能被声明为虚函数

61:C++的内部连接和外部连接

内部连接:如果一个名称对于它的编译单元来说是局部的,并且在连接时不会与其它编译单元中的同样的名称相冲突,那么这个名称有内部连接。

所有的声明
名字空间中的静态自由函数、静态友元函数、静态变量、const常量
enum定义
inline函数定义
类的定义
union的定义

外部连接:在一个多文件程序中,如果一个名称在连接时可以和其它编译单元交互,那么这个名称就有外部连接。

类非inline函数总有外部连接。包括类成员函数,类静态成员函数。
类静态成员变量总有外部连接。
名字空间中的非静态自由函数,非静态友元函数,非静态变量。

62:变量的分类,全局变量和局部变量的区别?是如何实现的?操作系统和编译器是怎么知道的?static全局变量与普通的全局变量有什么区别?static局部变量与普通变量有什么区别?static函数与普通函数有什么区别?

变量的分类

全局变量、局部变量、静态全局变量、静态局部变量。

全局与局部的区别

全局变量在整个工程文件内都生效;

静态全局变量只在定义它的文件内有效;

局部变量只在定义它的函数内有效,这个函数结束后失效;

静态局部变量只在定义它的函数内有效,程序分配一次内存,函数返回后,该变量不会消失,直到程序运行结束后才释放;

全局变量和静态变量如果没有初始化,则由编译器初始化为0。

静态全局变量是定义存储类型为静态型的外部变量,其作用域是从定义点到程序结束,所不同的是存储类型决定了存储地点。静态型变量是存放在内存的数据区中的,它们在程序开始运行前就分配了固定的字节,在程序运行过程中被分配的字节大小是不改变的。只有程序结束,才会释放所占用的内存。

变量的作用域

形参变量只有在被调用期间才分配内存单元,调用结束后立即释放。

这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。

局部变量也称内部变量,其作用域仅局限于函数内部,离开该函数再使用这种变量是非法的。

全局变量也称外部变量,属于一个源程序文件。其作用域是整个源程序。

全局变量特点

外部变量可加强函数模块之间的数据联系,但是又使函数要以来这些变量,使函数独立性降低。

从模块设计来讲是不利的,不必要的时候尽量不要使用全局变量。

在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。

变量中的存储分为“静态存储”和“动态存储”

静态存储的变量通常是在变量定义时就分定了存储单元并一直保持不变,直至整个程序结束。

动态存储变量是在程序执行过程中,使用它时才会分配存储单元,使用完毕后立即释放。

如果一个函数被多次调用,则反复的分配、释放形参变量的存储单元。

我们把这种由于变量存储方式不同而产生的特性称为变量的生存期

生存期表示了变量存在的时间。生存期和作用域是从时间和空间这两个不同的角度描述变量的特性的,两者有联系,又有区别。

变量属于哪种存储方式

从作用域看:

全局变量有全局作用域。全局变量只在一个源文件中定义,就可以作用于所有的源文件。当然其它不包含全局变量定义的源文件只需要用extern关键字再次声明这个全局变量即可。

静态局部变量具有局部作用域,它只被初始化一次,程序结束前一直存在。只对定义自己的函数体始终可见。

局部变量具有局部作用域,自动对象(auto),它在函数执行期间存在。函数调用执行结束,变量被销毁,占用内存也被回收。

静态全局变量具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它的作用域定义在它所在的文件里,不能作用到其它文件里。这样不同文件定义相同变量名的静态全局变量,仍是两个不同的变量。

从分配的内存空间看:

全局变量,局部静态变量,静态全局变量都在静态存储区分配空间,而局部在栈内分配空间。

全局变量本身就是静态存储方式;静态全局变量当然也是静态存储方式,可以避免在其它源文件中引起错误。

⭐程序的局部变量存在在堆栈中,全局变量存在在静态区中,动态申请数据存在于中。

63:异常框架

异常是程序在执行期间产生的问题。C++异常是指在程序运行时发生的特殊情况,比如尝试除以0等操作。

异常提供了一种转移程序控制权的方式。C++异常处理涉及到三个关键字:try、catch、throw。

throw当问题出现时,程序会抛出一个异常。这是通过throw关键字来完成的。

catch在想要处理问题的地方,通过异常处理程序捕获异常。

try块中的代码标识将被激活特定的异常。后面通常跟着一个或多个catch块。

如果有一个块抛出一个异常,捕获异常的方法会使用try和catch关键字。

try块中放置可能抛出异常的代码,try块的代码被称为保护代码。

posted @ 2023-03-22 20:27  iuk11  阅读(50)  评论(0编辑  收藏  举报