《逆袭进大厂》第三弹之C++提高篇79问79答(中)

简介: 笔记

129、当程序中有函数重载时,函数的匹配原则和顺序是什么?


1)  名字查找

2)  确定候选函数

3)  寻找最佳匹配


130、定义和声明的区别


如果是指变量的声明和定义

从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存。

如果是指函数的声明和定义

声明:一般在头文件里,对编译器说:这里我有一个函数叫function() 让编译器知道这个函数的存在。

定义:一般在源文件里,具体就是函数的实现过程 写明函数体。


131、全局变量和static变量的区别


1、全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。

全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。

这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。

而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。

static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。

2.static函数与普通函数有什么区别?

static函数与普通的函数作用域不同。尽在本文件中。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。

对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。

static函数与普通函数最主要区别是static函数在内存中只有一份,普通静态函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)


132、 静态成员与普通成员的区别是什么?


1)  生命周期

静态成员变量从类被加载开始到类被卸载,一直存在;

普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;

2)  共享方式

静态成员变量是全类共享;普通成员变量是每个对象单独享用的;

3)  定义位置

普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;

4)  初始化位置

普通成员变量在类中初始化;静态成员变量在类外初始化;

5)  默认实参

可以使用静态成员变量作为默认实参,


133、说一下你理解的 ifdef   endif代表着什么?


1)  一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

2)  条件编译命令最常见的形式为:

\#ifdef 标识符 
 程序段1 
 \#else 
 程序段2 
 \#endif

它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。

其中#else部分也可以没有,即:

\#ifdef 
 程序段1 
 \#denif

3)  在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量“重定义”错误。

在头文件中使用#define、#ifndef、#ifdef、#endif能避免头文件重定义。


134、隐式转换,如何消除隐式转换?


1、C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换

2、C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。在比如,数值和布尔类型的转换,整数和浮点数的转换等。

某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。

3、 基本数据类型 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。自定义对象 子类对象可以隐式的转换为父类对象。

4、 C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。

5、如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。


135、 虚函数的内存结构,那菱形继承的虚函数内存结构呢


参考:https://blog.csdn.net/haoel/article/details/1948051/

菱形继承的定义是:两个子类继承同一父类,而又有子类同时继承这两个子类。例如a,b两个类同时继承c,但是又有一个d类同时继承a,b类。


136、多继承的优缺点,作为一个开发者怎么看待多继承


1) C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。

2) 多重继承的优点很明显,就是对象可以调用多个基类中的接口;

3) 如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性

4) 加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。

5) 使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。


137、迭代器:++it、it++哪个好,为什么


1)  前置返回一个引用,后置返回一个对象

// ++i实现代码为:
int& operator++()
{
  *this += 1;
  return *this;
}

2)  前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低

//i++实现代码为:                 
int operator++(int)                 
{
int temp = *this;                   
   ++*this;                       
   return temp;                  
}


138、C++如何处理多个异常的?


1)  C++中的异常情况:

语法错误(编译错误):比如变量未定义、括号不匹配、关键字拼写错误等等编译器在编译时能发现的错误,这类错误可以及时被编译器发现,而且可以及时知道出错的位置及原因,方便改正。

运行时错误:比如数组下标越界、系统内存不足等等。这类错误不易被程序员发现,它能通过编译且能进入运行,但运行时会出错,导致程序崩溃。为了有效处理程序运行时错误,C++中引入异常处理机制来解决此问题。

2)  C++异常处理机制:

异常处理基本思想:执行一个函数的过程中发现异常,可以不用在本函数内立即进行处理, 而是抛出该异常,让函数的调用者直接或间接处理这个问题。

C++异常处理机制由3个模块组成:try(检查)、throw(抛出)、catch(捕获)

抛出异常的语句格式为:throw 表达式;如果try块中程序段发现了异常则抛出异常。

~cpp try { 可能抛出异常的语句;(检查) } catch(类型名[形参名])//捕获特定类型的异常 { //处理1;} catch(类型名[形参名])//捕获特定类型的异常 { //处理2;} catch(…)//捕获所有类型的异常 { }~


139、模板和实现可不可以不写在一个文件里面?为什么?


因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。

但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。

《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,

它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。


140、在成员函数中调用delete this会出现什么问题?对象还可以使用吗?


1、在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前是哪个对象在调用它。当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。

2、为什么是不可预期的问题?

delete this之后不是释放了类对象的内存空间了么,那么这段内存应该已经还给系统,不再属于这个进程。照这个逻辑来看,应该发生指针错误,无访问权限之类的令系统崩溃的问题才对啊?这个问题牵涉到操作系统的内存管理策略。delete this释放了类对象的内存空间,但是内存空间却并不是马上被回收到系统中,可能是缓冲或者其他什么原因,导致这段内存空间暂时并没有被系统收回。

此时这段内存是可以访问的,你可以加上100,加上200,但是其中的值却是不确定的。当你获取数据成员,可能得到的是一串很长的未初始化的随机数;访问虚函数表,指针无效的可能性非常高,造成系统崩溃。

3、 如果在类的析构函数中调用delete this,会发生什么?

会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。


141、如何在不使用额外空间的情况下,交换两个数?你有几种方法


1)  算术
x = x + y;
 y = x - y;
x = x - y; 
2)  异或
x = x^y;// 只能对int,char..
 y = x^y;
 x = x^y;
 x ^= y ^= x;


142、你知道strcpy和memcpy的区别是什么吗?


1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。

2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy


143、程序在执行int main(int argc, char *argv[])时的内存结构,你了解吗?


参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针

char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。


144、volatile关键字的作用?


volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile用在如下的几个地方:

1) 中断服务程序中修改的供其它程序检测的变量需要加volatile;

2) 多任务环境下各任务间共享的标志应该加volatile;

3) 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;


145、如果有一个空类,它会默认添加哪些函数?


1)  Empty(); // 缺省构造函数//
2)  Empty( const Empty& ); // 拷贝构造函数//
3)  ~Empty(); // 析构函数//
4)  Empty& operator=( const Empty& ); // 赋值运算符//

146、C++中标准库是什么?


1)  C++ 标准库可以分为两部分:

标准函数库:这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。

面向对象类库:这个库是类及其相关函数的集合。

2)   输入/输出 I/O、字符串和字符处理、数学、时间、日期和本地化、动态分配、其他、宽字符函数

3)   标准的 C++ I/O 类、String 类、数值类、STL 容器类、STL 算法、STL 函数对象、STL 迭代器、STL 分配器、本地化库、异常处理类、杂项支持库


147、你知道const char* 与string之间的关系是什么吗?


1)  string 是c++标准库里面其中一个,封装了对字符串的操作,实际操作过程我们可以用const char*给string类初始化

2)  三者的转化关系如下所示:

a)  string转const char* 
string s = “abc”; 
const char* c_s = s.c_str(); 
b)  const char* 转string,直接赋值即可 
const char* c_s = “abc”; 
 string s(c_s); 
c)  string 转char* 
 string s = “abc”; 
 char* c; 
 const int len = s.length(); 
 c = new char[len+1]; 
 strcpy(c,s.c_str()); 
d)  char* 转string 
 char* c = “abc”; 
 string s(c); 
e)  const char* 转char* 
 const char* cpc = “abc”; 
 char* pc = new char[strlen(cpc)+1]; 
 strcpy(pc,cpc);
f)  char* 转const char*,直接赋值即可 
 char* pc = “abc”; 
 const char* cpc = pc;


148、为什么拷贝构造函数必须传引用不能传值?


1) 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。

2) 参数传递过程到底发生了什么?

将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!

i)值传递:

对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);

对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);

如void foo(class_type obj_local){}, 如果调用foo(obj); 首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用

ii)引用传递:

 无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).

上述1) 2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。

拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。


149、你知道空类的大小是多少吗?


1)  C++空类的大小不为0,不同编译器设置不一样,vs设置为1;

2)  C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址;

3)  带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定;

4)  C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。


150、你什么情况用指针当参数,什么时候用引用,为什么?


1)  使用引用参数的主要原因有两个:

程序员能修改调用函数中的数据对象

通过传递引用而不是整个数据–对象,可以提高程序的运行速度

2)  一般的原则:

对于使用引用的值而不做修改的函数:

如果数据对象很小,如内置数据类型或者小型结构,则按照值传递;

如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针;

如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间;

如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);

3)  对于修改函数中数据的函数:

如果数据是内置数据类型,则使用指针

如果数据对象是数组,则只能使用指针

如果数据对象是结构,则使用引用或者指针

如果数据是类对象,则使用引用


151、静态函数能定义为虚函数吗?常函数呢?说说你的理解


1、static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。

2、静态与非静态成员函数之间有一个主要的区别,那就是静态成员函数没有this指针。

虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr。

这就是为何static函数不能为virtual,虚函数的调用关系:this -> vptr -> vtable ->virtual function


152、this指针调用成员变量时,堆栈会发生什么变化?


当在类的非静态成员函数访问类的非静态成员时,编译器会自动将对象的地址传给作为隐含参数传递给函数,这个隐含参数就是this指针。

即使你并没有写this指针,编译器在链接时也会加上this的,对各成员的访问都是通过this的。

例如你建立了类的多个对象时,在调用类的成员函数时,你并不知道具体是哪个对象在调用,此时你可以通过查看this指针来查看具体是哪个对象在调用。This指针首先入栈,然后成员函数的参数从右向左进行入栈,最后函数返回地址入栈。


153、你知道静态绑定和动态绑定吗?讲讲?


1)  对象的静态类型:对象在声明时采用的类型。是在编译期确定的。

2)  对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。

3)  静态绑定:绑定的是对象的静态类型,某特性(比如函数依赖于对象的静态类型,发生在编译期。

4)  动态绑定:绑定的是对象的动态类型,某特性(比如函数依赖于对象的动态类型,发生在运行期。


154、如何设计一个类计算子类的个数?


1、为类设计一个static静态变量count作为计数器;

2、类定义结束后初始化count;

3、在构造函数中对count进行+1;

4、 设计拷贝构造函数,在进行拷贝构造函数中进行count +1,操作;

5、设计复制构造函数,在进行复制函数中对count+1操作;

6、在析构函数中对count进行-1;


155、怎么快速定位错误出现的地方


1、如果是简单的错误,可以直接双击错误列表里的错误项或者生成输出的错误信息中带行号的地方就可以让编辑窗口定位到错误的位置上。

2、对于复杂的模板错误,最好使用生成输出窗口。

多数情况下出发错误的位置是最靠后的引用位置。如果这样确定不了错误,就需要先把自己写的代码里的引用位置找出来,然后逐个分析了。


156、虚函数的代价?


1)  带有虚函数的类,每一个类会产生一个虚函数表,用来存储指向虚成员函数的指针,增大类;

2)  带有虚函数的类的每一个对象,都会有有一个指向虚表的指针,会增加对象的空间大小;

3)  不能再是内敛的函数,因为内敛函数在编译阶段进行替代,而虚函数表示等待,在运行阶段才能确定到低是采用哪种函数,虚函数不能是内敛函数。


157、类对象的大小受哪些因素影响?


1)  类的非静态成员变量大小,静态成员不占据类的空间,成员函数也不占据类的空间大小;

2)  内存对齐另外分配的空间大小,类内的数据也是需要进行内存对齐操作的;

3)  虚函数的话,会在类对象插入vptr指针,加上指针大小;

4)  当该该类是某类的派生类,那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中,也会对派生类进行扩展。


158、移动构造函数听说过吗?说说


1)  有时候我们会遇到这样一种情况,我们用对象a初始化对象b后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;

2)  拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制;

3)  C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况;

4)  与拷贝类似,移动也使用一个对象的值设置另一个对象的值。但是,又与拷贝不同的是,移动实现的是对象值真实的转移(源对象到目的对象):源对象将丢失其内容,其内容将被目的对象占有。移动操作的发生的时候,是当移动值的对象是未命名的对象的时候。

这里未命名的对象就是那些临时变量,甚至都不会有名称。典型的未命名对象就是函数的返回值或者类型转换的对象。使用临时对象的值初始化另一个对象值,不会要求对对象的复制:因为临时对象不会有其它使用,因而,它的值可以被移动到目的对象。做到这些,就要使用移动构造函数和移动赋值:当使用一个临时变量对象进行构造初始化的时候,调用移动构造函数。类似的,使用未命名的变量的值赋给一个对象时,调用移动赋值操作;

5)

Example6 (Example6&& x) : ptr(x.ptr) 
  {
    x.ptr = nullptr;
  }
  // move assignment
  Example6& operator= (Example6&& x) 
  {
   delete ptr; 
   ptr = x.ptr;
   x.ptr=nullptr;
    return *this;
}


159、 什么时候合成构造函数?都说一说,你知道的都说一下


1)  如果一个类没有任何构造函数,但他含有一个成员对象,该成员对象含有默认构造函数,那么编译器就为该类合成一个默认构造函数,因为不合成一个默认构造函数那么该成员对象的构造函数不能调用;

2)  没有任何构造函数的类派生自一个带有默认构造函数的基类,那么需要为该派生类合成一个构造函数,只有这样基类的构造函数才能被调用;

3)  带有虚函数的类,虚函数的引入需要进入虚表,指向虚表的指针,该指针是在构造函数中初始化的,所以没有构造函数的话该指针无法被初始化;

4)  带有一个虚基类的类

还有一点需要注意的是:

1)  并不是任何没有构造函数的类都会合成一个构造函数

2)  编译器合成出来的构造函数并不会显示设定类内的每一个成员变量


160、那什么时候需要合成拷贝构造函数呢?


有三种情况会以一个对象的内容作为另一个对象的初值:

1)  对一个对象做显示的初始化操作,X xx = x;

2)  当对象被当做参数交给某个函数时;

3)  当函数传回一个类对象时;

1)  如果一个类没有拷贝构造函数,但是含有一个类类型的成员变量,该类型含有拷贝构造函数,此时编译器会为该类合成一个拷贝构造函数;

2)  如果一个类没有拷贝构造函数,但是该类继承自含有拷贝构造函数的基类,此时编译器会为该类合成一个拷贝构造函数;

3)  如果一个类没有拷贝构造函数,但是该类声明或继承了虚函数,此时编译器会为该类合成一个拷贝构造函数;

4)  如果一个类没有拷贝构造函数,但是该类含有虚基类,此时编译器会为该类合成一个拷贝构造函数;


161、成员初始化列表会在什么时候用到?它的调用过程是什么?


1)  当初始化一个引用成员变量时;

2)  初始化一个const成员变量时;

3)  当调用一个基类的构造函数,而构造函数拥有一组参数时;

4)  当调用一个成员类的构造函数,而他拥有一组参数;

5)  编译器会一一操作初始化列表,以适当顺序在构造函数之内安插初始化操作,并且在任何显示用户代码前。list中的项目顺序是由类中的成员声明顺序决定的,不是初始化列表中的排列顺序决定的。


162、构造函数的执行顺序是什么?


1)  在派生类构造函数中,所有的虚基类及上一层基类的构造函数调用;

2)  对象的vptr被初始化;

3)  如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;

4)  执行程序员所提供的代码;


163、一个类中的全部构造函数的扩展过程是什么?


1)  记录在成员初始化列表中的数据成员初始化操作会被放在构造函数的函数体内,并与成员的声明顺序为顺序;

2)  如果一个成员并没有出现在成员初始化列表中,但它有一个默认构造函数,那么默认构造函数必须被调用;

3)  如果class有虚表,那么它必须被设定初值;

4)  所有上一层的基类构造函数必须被调用;

5)  所有虚基类的构造函数必须被调用。


164、哪些函数不能是虚函数?把你知道的都说一说


1)  构造函数,构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类有一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;

2)  内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数;

3)  静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。

4)  友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。

5)  普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。


165、说一说strcpy、sprintf与memcpy这三个函数的不同之处


1)  操作对象不同

①   strcpy的两个操作对象均为字符串

②   sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串

③   memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。

2)  执行效率不同

memcpy最高,strcpy次之,sprintf的效率最低。

3)  实现功能不同

①   strcpy主要实现字符串变量间的拷贝

②   sprintf主要实现其他数据类型格式到字符串的转化

③   memcpy主要是内存块间的拷贝。


166、将引用作为函数参数有哪些好处?


1)  传递引用给函数与传递指针的效果是一样的。

这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

2)  使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;

而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;

如果传递的是对象,还将调用拷贝构造函数。

因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

3)  使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;

另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。


167、你知道数组和指针的区别吗?


1)  数组在内存中是连续存放的,开辟一块连续的内存空间;数组所占存储空间:sizeof(数组名);数组大小:sizeof(数组名)/sizeof(数组元素数据类型);

2)  用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。

3)  编译器为了简化对数组的支持,实际上是利用指针实现了对数组的支持。具体来说,就是将表达式中的数组元素引用转换为指针加偏移量的引用。

4)  在向函数传递参数的时候,如果实参是一个数组,那用于接受的形参为对应的指针。也就是传递过去是数组的首地址而不是整个数组,能够提高效率;

5)  在使用下标的时候,两者的用法相同,都是原地址加上下标值,不过数组的原地址就是数组首元素的地址是固定的,指针的原地址就不是固定的。


168、如何阻止一个类被实例化?有哪些方法?


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

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

相关文章
|
10天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
37 4