C++问题总结(类)

牛客网上面试题

类:

类的基本思想

1、数据抽象和封装

2、数据抽象:一种依赖于接口和实现分离的编程技术

3、封装:实现类的接口和实现分离

首先定义一个抽象数据类型(类的设计者负责考虑类的实现过程,类的使用者只需要抽象地思考类型做了什么,无须了解类型的工作细节)

封装的优点:

1、确保用户代码不会无意间破坏封装对象的状态

2、被封装的类的具体实现细节可以随时改变,无须调整用户级别的代码

抽象数据类型

1、不允许类的用户直接访问它的数据成员

2、定义在类内部的函数是隐式的inline函数

 

类作用域和成员函数

1、类本身就是一个作用域,类的成员函数的定义嵌套在类的作用域之内

2、即便bookNo定义在isbn之后,isbn还是可以使用bookNo,why???

因为类的定义分两步处理:

  • 首先,编译成员的声明
  • 直到类全部可见后才编译函数体
拷贝函数
 
拷贝、赋值和析构

拷贝:初始化变量、以值的方式传递或者返回一个对象等(当初始化一个非引用类型的变量时,初始值被拷贝给变量;返回值用于初始化调用点的一个临时量,该临时量就是函数调用的结果)

赋值:使用了赋值运算符

赋值运算符
赋值运算符的左侧运算对象必须是一个可修改的左值
赋值与初始化区别

1、赋值操作是在两个已经存在的对象间进行的,而初始化是要创建一个新的对象,并且其初值来源于另一个已存在的对象。编译器会区别这两种情况,赋值的时候调用重载的赋值运算符,初始化的时候调用拷贝构造函数。如果类中没有拷贝构造函数,则编译器会提供一个默认的。这个默认的拷贝构造函数只是简单地复制类中的每个成员。

2、对于基本数据类型赋值与初始化差别不大,但是对于大对象差别很多

3、在条件允许的情况下最好在初始化的时候就赋值,而尽量避免用=号赋值了,比如用成员初始化列表来初始化成员数据,不在构造函数里用赋值操作给成员数据.

 

 

 

访问控制与封装

C++语言中,使用访问说明符加强类的封装性

1、public:整个程序内可被访问,定义类的接口

2、private:可被类的成员函数访问,但不能被类的用户访问,封装了类的实现细节

第13章 拷贝控制

对应:

对象拷贝、移动、赋值、销毁(拷贝控制操作)

拷贝构造函数、移动构造函数:定义了用同类型的另一个对象初始化本对象时做什么(创建一个新对象)

拷贝赋值运算符、移动赋值运算符:定义了将一个对象赋予给同类型的另一对象做什么(两个已经存在的对象)

析构函数:定义了当此类型对象销毁时做什么

拷贝构造函数

一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,此构造函数为拷贝构造函数

为啥必须是引用???

       如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。
       需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),也是不行的。事实上,只有传引用不是传值外,其他所有的传递方式都是传值。

合成的拷贝构造函数

1、如果没有为类定义拷贝构造函数,编译器会为我们定义一个。但是不同于合成默认构造函数,即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。

2、合成的拷贝构造函数将每个非static成员拷贝到正在创建的对象中。

3、成员类型决定了如何拷贝:对类类型成员,使用其拷贝构造函数来拷贝,内置类型则直接拷贝;不能直接拷贝一个数组,但合成的默认构造函数会逐元素地拷贝一个数组类型的成员

不允许拷贝和赋值数组
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值

 

拷贝初始化与直接初始化的区别

形式上:

1、使用=初始化一个变量,执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去

2、不使用=,执行的是直接初始化

深层次上:

1、直接初始化,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数

2、拷贝初始化,要求编译器将右侧运算对象拷贝到正在创建的对象,如果需要还要进行类型转换!

拷贝初始化依赖拷贝构造函数和移动构造函数

 

拷贝初始化不仅发生在用=定义变量时,还发生在:

1、将一个对象作为实参传递给一个非引用类型的形参

2、从一个返回类型为非引用类型的函数返回一个对象

拷贝初始化的限制
见程序
拷贝赋值运算符

1、作用:类控制对象如何赋值

2、为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用

重载赋值运算符

1、重载运算符本质上是函数,名字由operator关键字后解接表示要定义的运算符的符号组成

2、赋值运算符就是一个名为operator=的函数

3、一个运算符如果是一个成员函数,其左侧运算对象就绑定到隐式的this指针;对于一个二元运算符,右侧运算对象作为显式参数传递

析构函数

1、构造函数初始化对象的非static数据成员;析构函数释放对象使用的资源,并销毁对象的非static数据成员

2、析构函数不接受任何参数,因此不能被重载;对于一个给定类,只有唯一一个析构函数

析构函数完成什么工作

1、构造函数:有一个初始化部分+函数体,成员初始化在函数体执行之前完成,且按照它们在类中出现的顺序进行初始化

2、析构函数:析构部分+函数体,首先执行函数体,然后销毁成员,成员按照初始化的顺序进行逆序销毁。析构部分是隐式的,销毁依赖于成员的类型:销毁类类型的成员需要执行成员自己的析构函数,内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。

3、隐式销毁一个内置指针类型的成员不会delete它所指向的对象;但是智能指针是类类型,具有析构函数,智能指针成员在析构阶段会被自动销毁。

4、当指向一个对象的引用或指针离开作用域,析构函数不会执行

 

什么时候会调用析构函数(见程序)

无论何时一个对象被销毁,就会自动调用其析构函数:

  • 变量在离开其作用域时被销毁
  • 当一个对象被销毁时,其成员被销毁
  • 容器(无论是标准库容器还是数组)被销毁时,其元素被销毁
  • 对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁
  • 对于临时对象,当创建它的完整表达式结束时被销毁
//拷贝构造函数
class Foo {
public:
    Foo();
    Foo(const Foo&);  
};

//不能用数组赋值或初始化另一个数组
int a[] = {0,1,2};
int a2[] = a;  //错误
a2 =a;    //错误


//拷贝初始化与直接初始化
string dots(10, '');  //直接。。。
string s2 = dots;     //拷贝
string nines = string(200, '9'); //拷贝。。


//拷贝初始化的限制
//首先,vector的接受单一大小参数的构造函数是explicit
vector<int> v1(10);   //正确:直接初始化,寻找最匹配的
vector<int> v2 = 10;  //错误

//拷贝赋值运算符
class Foo{
public:
    Foo& operator=(const Foo&);  //赋值运算符
};

//合成的拷贝构造函数与拷贝赋值运算符等价代码:
Primer P441,P444

//析构函数操作
{ //作用域
    //p和p2指向动态分配的对象
    Sales_data *p = new Sales_data;    //p是一个内置指针
    auto p2 = make_shared<Sales_data>(); //p2是一个智能指针
    Sales_data item(*p);                 //拷贝构造函数
    vector<Sales_data> vec;   //局部对象
    vec.push_back(*p2);       //拷贝
    delete p;                 //对P指向对象执行析构函数
}
//退出作用域之后,对p2, item, vec执行析构函数
//销毁p2会递减其引用次数;如果引用次数为0,对象被释放
//销毁vec会销毁其元素

1、C++的访问权限说明;编译器的底层是如何实现这些不同类型数据访问权限的;一个派生类对象访问了基类中没有权限的数据会怎么样

C++的访问权限说明?

public,private,protected

public:类的用户都可以访问

private:类的成员、友元可以访问

protected:类的成员、友元、派生类可以访问

一个派生类对象访问了基类中没有权限的数据会怎么样

报错,如果要访问该成员,方法有:

2、C++的多态性的体现;多态有什么用途?写一下虚函数动态绑定的实际C++示例代码;虚函数中的书写最好使用override 关键字,为什么要使用该关键字?虚函数是怎么实现动态绑定的?虚函数表的底层有了解过吗?具体使用了什么数据结构实现;

参考:https://blog.youkuaiyun.com/u013317445/article/details/103498372

C++的多态性的体现
  • 静态多态,包括函数重载、范型编程
  • 动态多态,主要是虚函数
多态的用途?
目的:**接口重用。**一个接口,多种形态。
比方,我们有个动物类,吃饭函数。一个接口:吃饭。继承的类有狗、羊、猫,它们也要吃饭,但是狗要吃骨头、羊要吃草、猫要吃鱼。我们就可以把父类动物类的吃饭函数加个virtual定义为虚函数。然后子类去重写(即重新定义)吃饭函数。用的时候,用父类的指针,指向任意一个子类对象,调用的时候去调用实际子类的成员函数,动物类指向猫则调用吃鱼,指向羊则调用吃草,指向狗则调用吃骨头。一个吃饭的接口,表现出了多种形态,吃鱼吃骨头吃草。
写一下虚函数动态绑定的实际C++示例代码
见程序
虚函数中的书写最好使用override 关键字,为什么要使用该关键字?
就加上关键字override 这样编译器可以辅助检查是不是正确重载,如果没加这个关键字 也没什么严重的error 只是少了编译器检查的安全性
虚函数是怎么实现动态绑定的?
通过虚函数表
虚函数表实现机制

虚函数表+虚表指针

编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针

 

V-table好比一张地图,指明了实际所应该调用的函数。
V-table也是虚函数的一个代价:产生系统开销。(弊)
编译器对每一个包含虚函数的类创建一个表。
当一个包含虚函数的类的对象被创建,会有一个虚表指针vptr(vpointer),指向该类的虚表,虚表里放的是一个类的虚函数的地址。是顺序存放虚函数地址的。

 

当虚函数没有重写的时候(没有实际意义),虚函数表按声明顺序放于表中;

当虚函数重写的时候,覆盖的f()函数被放到了虚表中原来父类虚函数f()的位置。没有被覆盖的虚函数依旧。

就有了,我们一般写的程序父类指针指向子类对象Base *b= new Derive(); b->f(); delete b;,实际调用的时候调用的是子类重写的函数Derive::f(),实现了多态。因为b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代。

为什么虚函数效率低?
因为虚函数需要一次间接寻址,而一般的函数可以在编译时定为到函数的地址,虚函数(动态类型调用)要根据某个指针定位到函数的地址。多了一个过程,效率低一些,但带来了运行时的多态
虚函数的入口地址和普通函数有什么区别?
虚函数表:
在这里插入图片描述
当一个包含虚函数的对象被创建的时候,
它在头部附加一个指针vptr,执行vtable
中相应位置。调用虚函数的时候,不管
你是用什么指针调用的,它先要去vtable
里找到入口地址在再执行
,从而实现了
“动态联编”。不像普通函数那样简单地
跳转到一个固定地址。

 

//基类
class Animal {
public:
    virutal void eat();
};

//派生类
class Cat : public Animal {
public:
    void eat() override;
};

class Dog : public Animal {
public:
    void eat() override;
};

Cat cat;
Dog dog;

Animal* p = &cat;
p.eat();  //调用cat重写的eat

Animal* p = &dog;
p.eat();  //调用dog重写的eat

 

3、什么是虚函数?什么是纯虚函数?基类为什么需要虚析构函数?

什么是虚函数?
  • c++成员函数有两种:一种是子类直接继承而不需要改变的函数,一种是允许子类重写/覆盖(override)的函数。把后者定义为了虚函数。加virtual关键字。
  • 虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要再派生类中声明该方法为虚方法。
什么是纯虚函数?
纯虚函数声明:virtual returntype func(parameter)=0;引入纯虚函数是为了派生接口。
基类为什么需要虚析构函数?(Effective c++ 条款7)

1、polymorphic(带多态性质)base classed应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数

2、classes的设计如果不是作为base classes使用,或者不是为了具备多态性polymorphic,就不该声明virtual析构函数

  • 对第一点:基类带一个non-virtual析构函数时,当一个base class指针指向一个派生类对象,而派生类对象经由基类指针删除时,会导致派生类对象局部销毁”,资源泄露。具体:派生类对象中基类成分被销毁,但是派生类成分很可能没被销毁,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的;
  • 解决方法:给基类一个virtual析构函数,析构函数运作方式:最深层派生的那个class其析构函数最先被调用,然后是其每个bass class的析构函数被调用
  • 对于第二点,当有virtual函数时,对象必须携带信息,用来在运行期决定哪个virtual函数被调用。这份信息由vptr虚表指针指出;每一个带有vitual函数的class都有一个相应的虚表,实例化对象时,每一个对象有一个对应的vptr,会增加对象大小

4、struct与class的区别?

struct与class的区别?
本质区别是访问的默认控制:默认的继承访问权限,class是private,struct是public;

5、派生类中构造函数,析构函数调用顺序?

派生类中构造函数,析构函数调用顺序?
构造函数:“先基后派”;析构函数:“先派后基”。

6、C++类中数据成员初始化顺序?

构造函数再探(C++ Primer)

构造函数初始值列表
如果没有在构造函数的初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化
构造函数地初始值有时必不可少(程序见primer p258)
如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值
成员初始化顺序

1、成员变量在使用构造函数初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与类中定义成员变量的出现顺序有关。

2、类中const、引用、属于某种未提供默认构造函数的类类型的成员常量必须在构造函数初始化列表中初始化

3、类中static成员变量,只能在类外初始化(同一类的所有实例共享静态成员变量)。

 

7、结构体内存对齐问题?结构体/类大小的计算?

8、计算类大小例子

9、友元函数和友元类

参考:友元函数和友元类

友元提供了不同类的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。通过友元,一个不同函数或另一个类中的成员函数可以访问类中的私有成员和保护成员。c++中的友元为封装隐藏这堵不透明的墙开了一个小孔,外界可以通过这个小孔窥视内部的秘密。

 

友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。

友元函数
  • 友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend
  • 友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的
友元类
  • 友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
注意

(1) 友元关系不能被继承。

(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

10、什么情况下,类的析构函数应该声明为虚函数?为什么?哪些函数不能成为虚函数?

什么情况下,类的析构函数应该声明为虚函数?
基类具有多态性质时,应该声明virtual析构函数,为什么?防止当基类指针指向派生类对象,用基类指针销毁派生类对象时,只调用基类的析构函数,没有执行派生类的析构函数,导致局部销毁
哪些函数不能成为虚函数?

不能被继承的函数和不能被重写的函数。

  • 普通函数(普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写)
  • 友元函数(友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法)
  • 构造函数(多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。)
  • 内联成员函数(内联函数就是为了在代码中直接展开,减少函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。)
  • 静态成员函数(静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写)

11、编写一个有构造函数,析构函数,赋值函数,和拷贝构造函数的String类

 

12、this指针的理解

为啥使用this指针?
通常在class定义时要用到类型变量自身时,因为这时候还不知道变量名(为了通用也不可能固定实际的变量名),就用this这样的指针来使用变量自身。 
this指针好处
  • 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
  • 在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看成this的隐式使用
  • 当你进入一个房子后,
    你可以看见桌子、椅子、地板等,
    但是房子你是看不到全貌了。

    对于一个类的实例来说,
    你可以看到它的成员函数、成员变量,
    但是实例本身呢?
    this是一个指针,它时时刻刻指向你这个实例本身。

13、构造函数初始化列表

构造函数初始值列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
构造函数初始值列表跟不用初始值列表的区别(见程序)
  • 两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。
  • 初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
    首先把数据成员按类型分类并分情况说明:
    1.内置数据类型,复合类型(指针,引用)
        在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
    2.用户定义类型(类类型)
        结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成(先进行了一次隐式的默认构造函数调用),也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用了拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)。
有的时候必须用带有初始化列表的构造函数
  • 1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
  • 2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。 
//显式初始化列表与赋值方式
class Example {
public:
    int a;
    int b;
    //用初始化列表显式初始化类的成员
    Example():a(0), b(0.8){}
    //一次默认构造,一次赋值
    Example(){
        a = 0;
        b = 0.8;
    }
};

 

14、详解拷贝构造函数相关知识

好文章
c++拷贝构造函数详解
拷贝构造函数应用的场景:
  • 用一个对象初始化另外一个对象
  • 函数的参数是一个对象,并且是值传递方式
  • 函数的返回值是一个对象,并且是值传递方式(引用传递其实是个地址,地址是个指针类型,进行简单的赋值拷贝)
拷贝构造函数的调用时机

  1. 当函数的参数为类的对象时

大致步骤:

(1)将对象A传入形参时,会产生一个临时变量C

(2)调用拷贝构造函数,将A的值传给C

(3)执行完函数,析构掉C


2. 函数的返回值是类的对象

大致步骤:

(1)先产生一个临时变量xxx

(2)调用拷贝构造函数,将返回值temp传给xxx

(3)函数执行到最后,先析构temp,然后析构掉xxx

 

3. 对象需要通过另外一个对象进行初始化

CExample A(C);

浅拷贝与深拷贝
    拷贝有两种:深拷贝,浅拷贝。

      当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

     深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝

 

浅拷贝:简单的成员赋值

深拷贝:对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,但它们指向的空间具有相同的内容

参数传递过程到底发生了什么?
将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!
      i)值传递:
         对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
         对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj);  首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用
     ii)引用传递:
        无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).
在类中有指针数据成员时,拷贝构造函数的使用?
如果不显式声明拷贝构造函数的时候,编译器也会生成一个默认的拷贝构造函数,而且在一般的情况下运行的也很好。但是在遇到类有指针数据成员时就出现问题 了:因为默认的拷贝构造函数是按成员拷贝构造,这导致了两个不同的指针(如ptr1=ptr2)指向了相同的内存。当一个实例销毁时,调用析构函数 free(ptr1)释放了这段内存,那么剩下的一个实例的指针ptr2就无效了,在被销毁的时候free(ptr2)就会出现错误了, 这相当于重复释放一块内存两次。这种情况必须显式声明并实现自己的拷贝构造函数,来为新的实例的指针分配新的内存。
以下函数哪个是拷贝构造函数,为什么?
   解答:对于一个类X, 如果一个构造函数的第一个参数是下列之一:
   a) X&
   b) const X&
   c) volatile X&
   d) const volatile X&
   且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.
一个类中可以存在多于一个的拷贝构造函数吗?

类中可以存在超过一个拷贝构造函数。

 

注意,如果一个类中只存在一个参数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化.
  如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。
  这个默认的参数可能为 X::X(const X&)或 X::X(X&),由编译器根据上下文决定选择哪一个。

什么情况下必须定义拷贝构造函数?
当类的对象用于函数值传递时(值参数,返回类对象),拷贝构造函数会被调用。如果对象复制并非简单的值拷贝,那就必须定义拷贝构造函数。例如大的堆栈数据拷贝。如果定义了拷贝构造函数,那也必须重载赋值操作符。
X::X(const X&);   //拷贝构造函数
X::X(X); 
X::X(X&, int a=1);   //拷贝构造函数
X::X(X&, int a=1, int b=2);  //拷贝构造函数


//多个拷贝构造函数
class X { 
public: 
  X(const X&); // const 的拷贝构造 
  X(X&); // 非const的拷贝构造 
};

15、重载overload,覆盖override,重写overwrite,这三者之间的区别

重载overload,覆盖override,重写overwrite,这三者之间的区别
1)overload,将语义相近的几个函数用同一个名字表示,但是参数和返回值不同,这就是函数重载
特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无
2)override,派生类覆盖基类的虚函数,实现接口的重用
特征:不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字(必须是虚函数)
3)overwrite,派生类屏蔽了其同名的基类函数
特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字
overload重载

1、函数重载可以一定程度上减轻程序员起名字、记名字的负担,编译器会根据实参类型确定调用哪个函数

2、main函数不能重载

3、不允许两个函数除了返回类型外其它所有要素都相同

 

重载和const形参
  • 顶层const不影响传入函数的对象,一个拥有顶层const的形参无法跟另一个没有顶层const的形参区分开
  • 如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的
const_cast和重载(待补充理解!!!)

1、const_cast常用于函数重载

const_cast

1、const_cast只能改变运算对象的底层const(也只有const_cast能改变对象的常量属性,使用其它形式的命名强制类型转换改变表达式的常量属性将会引发编译器错误),不能用const_cast改变表达式的类型

2、常量指针被转化成非常量指针,转换后指针指向原来的变量(即转换后的指针地址不变)

2、用const_cast<>将一个const值绑定到一个非const引用上,并试图通过这个非const引用去修改原来的值是一个Undefined Reference。事实上,应该遵循这样的原则:使用const_cast去除const限定的目的绝不是为了修改它的内容(参考:https://blog.youkuaiyun.com/zhangxiao93/article/details/74857605

 

//形参中顶层const被忽略
Record lookup(Phone);
Record lookup(const Phone);   //重复声明了Record lookup(Phone);

Record lookup(Phone*);
Record lookup(Phone* const);   //重复声明了Record lookup(Phone*);


//底层const实现重载
Record lookup(Account&);
Record lookup(const Account&);   //作用于常量的引用

Record lookup(Account*);
Record lookup(const Account*);   //作用于指向常量的指针
//因为const不能转换为其它类型,所以只能把const对象传给const形参;但是非常量可以转换成const,所以上面4个函数都作用于非常量对象;编译器优先选择非常量版本的函数


//const_cast去掉常量属性
int main()
{
    const int c = 2;
#if 0
    int &cc = c;//error: 将int&类型的引用绑定到const int 时,限定符被丢弃
    int *pc = &c;//error: const int * 不能初始化int *实体
#endif

#if 1
    int & rc = const_cast<int &>(c);
    rc = 3;
    cout << "after cast" << endl;

    //地址相同
    cout << &c << endl;
    cout <<& rc << endl;
    //值不相同
    cout << c << endl;
    cout << rc << endl;
#endif
    system("pause");
    return 0;
}

 

16、静态绑定和动态绑定的介绍

1)对象的静态类型和动态类型
静态类型:对象在声明时采用的类型,在编译时确定
动态类型:当前对象所指的类型,在运行期决定,对象的动态类型可变,静态类型无法更改
2)静态绑定和动态绑定
静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定
动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运行期确定

17、引用是否能实现动态绑定,为什么引用可以实现

指针或引用是在运行期根据他们绑定的具体对象确定。

18、子类析构时要调用父类的析构函数吗?

派生类的析构函数在执行完后,会自动执行基类的析构函数,这个是编译器强制规定的,没有为什么,甚至你在析构函数里调用return都不会立即返回到调用处,而是会先按顺序把析构函数全部调用完。

19、有哪几种情况只能用intialization list 而不能用assignment?(3)

  • 1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
  • 2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值