概述
学习《深度探索C++对象模型》中的知识点纪要
构造函数语意学
构造函数有:默认构造函数(合成构造函数)、自定义构造函数、拷贝构造函数、委托构造函数
构造函数
- 构造函数的作用
类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要 类的对象被创建则构造函数就会被调用。构造函数有:默认构造函数(合成构造函数)、拷贝构造函数(拷贝构造函数中有默认拷贝构造函数即合成拷贝构造函数)、委托构造函数(就是构造函数声明后面加:(冒号)后调用另一个构造函数,之后就是委托构造函数的函数体) - 为什么构造函数不能有返回值
(1)假设有一个类C,有如下定义:
构造函数的调用之所以不设返回值,是因为构造函数的特殊性决定的。从基本语义角度来讲,构造函数返回的应当是所构造的对象。否则,我们将无法使用临时对象:
void f(int a) {…} //(1)
void f(const C& a) {…} //(2)
f(C()); //(3),究竟调用谁?
对于(3),我们希望调用的是(2),但如果C::C()有int类型的返回值,那么究竟是调(1)好呢,还是调用(2)好呢。于是,我们的重载体系,乃至整个的语法体系都会崩溃。
这里的核心是表达式的类型。目前,表达式C()的类型是类C。但如果C::C()有返回类型R,那么表达式C()的类型应当是R,而不是C,于是便会引发上述的类型问题。
(2)只是C++标准规定了构造/析构/自定义类型转换符不可以指定返回类型。 但你不能据此就说它们没有返回类型。
(3)本人的意见是构造函数是有返回值的,返回的就是新构造的对象本身,但是不能指定返回类型,因为你用这个类的构造函数表明就是返回这个类的一个对象,没有必要指定返回类型,即使是指定也必须是指定类本身的返回类型,这就多次一举了吧。
3)为什么构造函数不能为虚函数
虚函数调用的机制,是知道接口而不知道其准确对象类型的函数,但是创建一个对象,必须知道对象的准确类型;当一个构造函数被调用时,它做的首要事情之一就是初始化它的VPTR来指向VTABLE。
构造函数是不能在对象创建成功后用对象调用的,只有在对象创建时才可以被调用
class Constructor
{
public:
Constructor()
{
}
Constructor(int s32InputValue1)
{
m_s32Value1 = s32InputValue1;
}
Constructor(char a,int b):Constructor(b){}//委托构造函数
~Constructor()
{
}
private:
int m_s32Value1;
};
int main(int argc,char **argv)
{
Constructor l_clsConstructor;
l_clsConstructor.Constructor(1);
}
error: invalid use of ‘Constructor::Constructor’
只能用下面方式
Constructor *l_pclsConstructor = new Constructor(1);
Constructor clsConstructor (1);
Constructor *l_pclsConstructor = new Constructor();
Constructor clsConstructor ();
- 构造函数名称
构造函数的名字必须与类名相同,而且构造函数不能声明成const的,当我们创建一个const对象时,直到构造函数完成初始化过程(这里的初始化是指整个构造函数体执行完成,而不是初始值列表),对象才能正真取得其常量属性。 - 初始值列表
所谓初始值列表就是函数及函数体之间的部分,如
class Constructor
{
public:
Constructor()
{
}
Constructor(int s32InputValue1):m_s32Value1(s32InputValue1)
{
}
~Constructor()
{
}
private:
int m_s32Value1;
};
初始值列表就是:m_s32Value1(s32InputValue1),这个只是一个,初始值列表与构造体内部赋值是两个概念,初始值列表就是在申明的时候初始化,而构造函数体内是对成员赋值操作,所以构造函数体中有些操作是不允许的,必须在初始值列表中对其初始化,如只读成员变量、引用类型、带有参数类构造函数初始化的对象。必须在初始值列表中初始化,再有就是初始化顺序与初始值列表顺序无关,跟成员的定义顺序有关,从前向后定义的顺序初始化:
class Base
{
public:
Base(int s32InputValue)
{
m_s32Value = s32InputValue;
}
private:
int m_s32Value;
};
class Constructor
{
public:
Constructor(int s32InputValue1,int s32InputVaue2,int &s32InputValue3):
m_s32Value1(s32InputValue1),m_s32Value2(s32InputVaue2),m_s32Value3(s32InputValue3),m_clsBase(s32InputValue1){}
~Constructor()
{
}
private:
int m_s32Value1; //可以在构造函数体内初始化也可以在初始值列表中初始化
const int m_s32Value2; //必须在初始值列表中初始化
int &m_s32Value3; //必须在初始值列表中初始化
Base m_clsBase; //必须在初始值列表中初始化
};
默认构造函数
- 默认构造函数
默认构造函数,在C++ Annotated Reference Manual(ARM)[ELLIS90]中的section 12.1告诉我们;默认的构造函数在需要的时候被编译器生成,这里需要是有区分的,一个是程序需要,一个是用户需要(初始化成员等)。
class Foo
{
public:
int val;
Foo *pnext;
};
void foo_bar()
{
Foo bar;
if(bar.val || bar.pnext)
{
//
}
}
这段程序在书中说是不会产生默认的构造函数,但接下来说的话我认为是会产生默认的构造函数,这个需要验证,(C++标准(C+++ Standard[ISO-C++95])修改了对ARM所说的,对于class X,如果没有任何用户声明的构造函数,那么久有个隐式构造函数声明出来,一个隐式声明的构造函数是没啥用的)
C++标准又说了什么情况下这个隐式的构造函数是没有用的,什么情况这个隐式的构造函数是有用的
一下是4种情况说明什么情况下是有用的(这里所说的类是没有任何构造函数的)
例如:
1.类的成员中有类的成员,则这个类的默认构造成员是有用的
class A
{
public:
A();
};
class B
{
private:
A a;
}
2.带有默认的构造的基类派生出的子类,那么这个子类的隐式默认构造函数就是有用的
class C
{
public:
C();
}
class D :public C
{
}
3.带有一个虚函数的类
【1】 类声明或继承一个虚函数
【2】类派生自一个继承串链,其中有一个或更多的虚基类。
class x
{
public:
virtual int func(){};
private:
int i;
}
4.带有一个虚基类的类
class E: public x
{
private:
int j;
}
class x
{
private:
int i;
}
class E: public virtual x
{
private:
int j;
}
在默认构造函数后说明了这样一句话,以上四种情况编译器合成出默认的构造函数,c++标准把上面四种合成的构造函数称为有用的默认构造函数,至于没有存在哪四种情况,而又没有声明任何构造函数的类,我们说他们拥有的是隐式的没有用的构造函数,实际上并不会被合成。这里我加一句,只要有构造函数被显示声明,则编译器不会合成默认的构造函数。
- 定义默认构造函数-default
有时候我们需要默认的构造函数,我们可以告诉编译器为我们合成构造函数如:
class Constructor
{
public:
Constructor() = default;
Constructor(int s32InpuValue1)
{
m_s32Value = s32InpuValue1;
}
~Constructor();
private:
int m_s32Value;
};
=default;编译器为我们自动合成默认构造函数,无需我们自己定义。
拷贝构造函数
除了定义类的对象如何初始化外,类还需要控制拷贝、赋值、和销毁对象时的行为。对象在以下几种方式下会拷贝,如我们初始化变量以及以值的方式传递(函数参数)或返回一个对象(函数返回)等。
某些类不能依赖合成的拷贝,赋值和析构等操作,特别是当类需要分配类对象之外的资源时,合成的版本常常会失效。
- 拷贝构造函数
拷贝构造函数是构造函数的第一参数是自身类类型的引用,切任何额外参数都有默认值,则此构造函数是拷贝构造函数。- 合成拷贝构造函数
如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。合成拷贝构造函数与合成构造函数不同,类即使定义了其他构造函数,编译器也会合成默认的拷贝构造函数,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。每个成员的类型决定了他们如何拷贝:对类类型的成员,会使用其拷贝构造函数来拷贝;对内置类型的成员则直接拷贝。虽然我们不能直接拷贝一个数组,但合成拷贝构造函数会逐元素地拷贝一个数组类型的成员,如果数组元素是类类型,则使用元素的拷贝构造函数来进行拷贝。
编译器创建合成拷贝构造函数是有先决条件的,就跟创建合成的构造函数一样,如果类对象有位逐次拷贝语义则编译不会合成拷贝构造函数,
所谓没有位逐次拷贝语义的几种情况如下(这里的位指的是bit)
1.当类内含一个成员对象,而这个成员对象的类声明有一个拷贝构造函数时(不弄是类设计者显示声明或编译器默认合成),编译默认合成拷贝构造函数
2.当类继承自一个基类,而基类存在拷贝构造函数时(不论被显示声明还是编译器默认合成)
3.当类声明了一个或多个虚函数时。
4.当类派生自一个继承串链,其中有一个或多个虚基类时
不论有没位逐次拷贝语义,我们都可以要求编译器为我们合成默认的拷贝构造函数
- 合成拷贝构造函数
class Constructor
{
public:
Constructor(const Constructor &) = default; /./要求编译器为我们合成默认的拷贝构造函数,如果是类内这样声明,则为内联拷贝构造函数,声明类外则为非内联函数
Constructor(int s32InpuValue1)
{
}
~Constructor();
private:
};
我们也可以阻止拷贝,
及将上面的声明Constructor(const Constructor &) = default;改为Constructor(const Constructor &) = delete;这样就阻止拷贝,
函数声明后面跟=delete;意思是阻止函数的生成,对于拷贝赋值运算符一样适用;当然析构函数是不能定义为删除的,因为定义了析构函数删除的后就不能定义类对象,只能通过new创建对象,当new完后就不能delete 对象
这里再此说名直接初始化调用的是构造函数,拷贝初始化调用的是拷贝构造函数
拷贝赋值运算符
与类控制其对象如何初始化一样,类也可以控制其对象如何赋值,与拷贝构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器会为他合成一个。
class Constructor
{
public:
Constructor() = default;
Constructor(int s32InpuValue1)
{
m_s32Value = s32InpuValue1;
}
Constructor(const Constructor &pcls,int s32InputValue=0){};拷贝构造函数,可以不用const,但此参数总是以一个const的引用。
Constructor& operator=(const Constructor &ct){m_s32Value = ct.m_s32Value;return *this;}//拷贝赋值运算,编译器默认合成的也是这种
~Constructor();
private:
int m_s32Value;
};
合成拷贝运算符会将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中一次将每个非static成员拷贝到正在创建的对象中。每个成员的类型决定了它如何拷贝。对类类型的成员,会使用其拷贝构造函数来拷贝;内置类型的成员则直接拷贝。虽然我们不能直接拷贝一个数组,但合成拷贝构造函数会逐元素地拷贝一个数组的成员。如果数组元素是类类型,则使用元素的拷贝构造函数来进行拷贝。
说到这里不得不说下直接初始化及拷贝初始化初始化
首先说下初始化的几种方式
int w(0);//初始化物在小括号内
int x = 0; //初始化物在等号之后
int y{0}; //初始化物在大括号内
int z = {0} //使用等号和大括号指定初始化物
直接初始化和拷贝初始化
如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始化值拷贝到新创建的对象中去,与之相反,如果不使用等号,则执行的是直接初始化。
A:将一个对象作为实参传递个一个非引用类型的实参,虽然没有等号,但实际上执行的是拷贝初始化
B:从一个返回类型为非引用类型的函数返回一个对象,也是一种拷贝初始化,
C:用花括号列表初始化一个数组中的元素或一个聚合类中的成员时,容器会对其元素进行拷贝初始化
四种初始化的限制:
1.初始化类成员
class Constructor
{
public:
Constructor() = default;
Constructor(int s32InpuValue1)
{
m_s32Value = s32InpuValue1;
}
Constructor(Constructor &pcls,int s32InputValue=0){};//拷贝构造函数
~Constructor();
private:
int m_s32Value = 0; //正确
//int m_s32Value2(0);//不可行
int m_s32Value3{0}; //正确
int m_s32Value4={0}; //正确
};
2.初始化类类型
Constructor C1;//调用的是默认构造函数
Constructor C2 = C1;//并非初始化,调用的是复制构造函数
C1 = C2; /并非初始化,调用的是复制赋值运算符
3.容器初始化
std::vector<int> v{1,2,3,4};//v的初始内容为1,2,3,4
4.不可复制的对象可以用大括号或小括号初始化
std::atomic<int> ai1{1};//可行
std::atomic<int> ai2(1);//可行
std::atomic<int> ai3 = 0;//不可行
从上面可以看出大括号到处都可以初始化,但就是不能窄化(所谓窄化就是讲大范围数据拷贝到小范围变量中如double赋值给int,int赋值给char),
double f;
int a {f}; //不可行,但小括号和等号就可以,也有的编译器是可行的
int a = f;//可行
int b (f); //可行
int c = {f};//可行,会报警告
还有一个问题是圆括号的问题
Constructor C1(0);//调用的是Constructor的构造函数传入的是0
Constructor C2();//最令人苦恼之解析语法,这个语句申明了一个名为C2,返回一个Constructor型别对象的函数
还有一个:大括号初始化的缺陷在于伴随它有时会出现的意外行为。这种行为源于大括号初始化物、std::initializer_list以及构造函数重载决议之间的纠结关系。这几者之间的相互作用可以使得代码看起来是要做某一件事,但实际上是在做另外一件事。如果用大括号初始化一个auto申明的变量,那么推到出来的型别就会成为std::initializer_list,尽管用其他方式使用相同的初始化物来声明变量就能够得出更符合直觉的型别。
在构造函数中,只要形参中没有一个具备std::initializer_list型别,那么大括号和小括号就没区别
委托构造函数
C++11 新标准扩展了构造函数初始值的功能,使得我们可以定义所谓i的委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或全部)职责委托给了其他构造函数。
和其他构造函数一样,一个委托构造函数也有一个成员初始值的列表和一个函数体。在委托构造函数内,成员初始值列表只有一个唯一的入口,就时类名本身。和其他成员初始值一样,类名后面紧跟园括号阔起来的参数列表,参数列表必须与类中另外一个构造函数匹配。
/*#################################################################################
* #Description: CSDN 委托函数定义解释说明
* #version: v1.0.0
* #Author: lwh
* #Date: 2022-08-13 14:32:56
* #LastEditTime: 2022-08-13 15:46:20
* #FilePath: /CSDN/DelegatingConstructor.h
#################################################################################*/
#ifndef __DELEGATING_CONSTRUCTOR_H__
#define __DELEGATING_CONSTRUCTOR_H__
#include <stdio.h>
#define BOOK_NO_MAX_LEN 32 //书本编号最大长度
#define DEFINE_BOOK_NO "NO.12345" //默认的编号
#define DEFINE_BOOK_CNT 3 //默认的数量
#define DEFINE_BOOK_PRICE 24 //默认的价格
class DelegatConstruct
{
public:
/*构造函数*/
DelegatConstruct(const char *const ps8aBookNo, const unsigned int u32Cnt, const double dPrice):
m_u32Cnt(u32Cnt),m_dPrice(dPrice)
{
snprintf(m_s8aBookNo,BOOK_NO_MAX_LEN,"%s",ps8aBookNo);
printf("构造函数n");
}
/*委托构造函数1*/
DelegatConstruct(const char *const ps8aBookNo):DelegatConstruct(ps8aBookNo,DEFINE_BOOK_CNT,DEFINE_BOOK_PRICE)
{
printf("委托构造函数1n");
}
/*下面两个构造函数虽然委托的构造函数不同,但违反了函数重载,所以两个只能选一个,否则编译报错*/
#if 1 //
/*委托构造函数2*/
DelegatConstruct():DelegatConstruct(DEFINE_BOOK_NO)
{
printf("委托构造函数2n");
}
#else
/*委托构造函数2*/
DelegatConstruct():DelegatConstruct(DEFINE_BOOK_NO,DEFINE_BOOK_CNT,DEFINE_BOOK_PRICE)
{
printf("委托构造函数2n");
}
#endif
private:
char m_s8aBookNo[32];
unsigned int m_u32Cnt;
double m_dPrice;
};
#endif
int main(int argc,char *argv[])
{
printf("=========================n");
DelegatConstruct clsDelegConst("NO.34567",4,45); //构造函数
printf("=========================n");
DelegatConstruct clsDelegConst1("NO.34567"); //委托构造函数初始化
printf("=========================n");
DelegatConstruct clsDelegConst2; //委托构造函数1初始化
printf("=========================n");
}
运行结果:
=========================
构造函数
=========================
构造函数
委托构造函数1
=========================
构造函数
委托构造函数1
委托构造函数2
=========================
构造函数的default与delete关键字
- default 的含义
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生成对应的默认构造函数(默认的拷贝构造函数)。其中,=default既可以和声明一起出现在类内部,也可以作为定义出现在类外部。和其他函数一样,如果=default在类的内部,则默认构造函数是内联的,如果定义在类的外部,则该成员默认的情况下不是内联的。- 未显示声明生成默认构造函数=
#ifndef __ANIMAL_H__
#define __ANIMAL_H__
class Animal
{
public:
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
{
m_s32Eye = s32Eye;
m_s32Mouth = s32Mouth;
}
private:
int m_s32Eye;
int m_s32Mouth;
int m_s32Weight;
};
#endi
#include <stdio.h>
#include "Animal.h"
int main(int argc,char *argv[])
{
printf("=========================n");
Animal animal;
printf("=========================n");
}
编译结果:
$ make
mkdir ./obj/
g++ -c DelegatingConstructor.cpp -o ./obj/DelegatingConstructor.o -I ./
g++ -c main.cpp -o ./obj/main.o -I ./
main.cpp: In function ‘int main(int, char**)’:
main.cpp:15:12: error: no matching function for call to ‘Animal::Animal()’
Animal animal;
^~~~~~
In file included from main.cpp:10:0:
Animal.h:15:5: note: candidate: Animal::Animal(int, int, int)
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
^~~~~~
Animal.h:15:5: note: candidate expects 3 arguments, 0 provided
Animal.h:12:7: note: candidate: constexpr Animal::Animal(const Animal&)
class Animal
^~~~~~
Animal.h:12:7: note: candidate expects 1 argument, 0 provided
Animal.h:12:7: note: candidate: constexpr Animal::Animal(Animal&&)
Animal.h:12:7: note: candidate expects 1 argument, 0 provided
Makefile:29: recipe for target 'obj/mai
编译器没有生成默认构造函数,所以这里编译时会报错没有匹配的构造函数
- 显示声明生成默认构造函数
#ifndef __ANIMAL_H__
#define __ANIMAL_H__
class Animal
{
public:
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
{
m_s32Eye = s32Eye;
m_s32Mouth = s32Mouth;
}
Animal() = default;
private:
int m_s32Eye;
int m_s32Mouth;
int m_s32Weight;
};
#endif
这里main没有改变
$make
g++ -c main.cpp -o ./obj/main.o -I ./
g++ ./obj/DelegatingConstructor.o ./obj/main.o -o ./demo -I ./
./demo
=========================
=========================
- delete 含义
大多数类应该定义默认构造函数,、拷贝构造函数和拷贝运算赋值运算符,但有写类定义默认的构造函数就没有意义,如iostream类阻止了拷贝
#ifndef __ANIMAL_H__
#define __ANIMAL_H__
class Animal
{
public:
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
{
m_s32Eye = s32Eye;
m_s32Mouth = s32Mouth;
}
Animal(Animal &animal) = default;
Animal() = default; // 显示告诉编译器生成默认的构造函数
private:
int m_s32Eye;
int m_s32Mouth;
int m_s32Weight;
};
#endi
#include <stdio.h>
#include "Animal.h"
int main(int argc,char *argv[])
{
printf("=========================n");
Animal monkey;
Animal orangutan(monkey); //拷贝构造函数
printf("=========================n");
}
$ make
mkdir ./obj/
g++ -c DelegatingConstructor.cpp -o ./obj/DelegatingConstructor.o -I ./
g++ -c main.cpp -o ./obj/main.o -I ./
g++ ./obj/DelegatingConstructor.o ./obj/main.o -o ./demo -I ./
$ ./demo
=========================
=========================
这里显示声明让编译器合成默认的拷贝构造函数能沟正常编译,但如果这里阻止拷贝呢:
#ifndef __ANIMAL_H__
#define __ANIMAL_H__
class Animal
{
public:
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
{
m_s32Eye = s32Eye;
m_s32Mouth = s32Mouth;
}
Animal(Animal &animal) = delete;
Animal() = default; // 显示告诉编译器生成默认的构造函数
private:
int m_s32Eye;
int m_s32Mouth;
int m_s32Weight;
};
#endif
这里main没有变
$ make
mkdir ./obj/
g++ -c DelegatingConstructor.cpp -o ./obj/DelegatingConstructor.o -I ./
g++ -c main.cpp -o ./obj/main.o -I ./
main.cpp: In function ‘int main(int, char**)’:
main.cpp:16:28: error: use of deleted function ‘Animal::Animal(Animal&)’
Animal orangutan(monkey); //拷贝构造函数
^
In file included from main.cpp:10:0:
Animal.h:20:5: note: declared here
Animal(Animal &animal) = delete;
^~~~~~
Makefile:29: recipe for target 'obj/main.o' failed
make: *** [obj/main.o] Error 1
注意:delete与default不能同时声明同一个构造函数,否则编译器报错,但可以同时在一个类中声明不同的默认构造函数
构造函数在继承中的调用顺序
调用派生类的构造函数之前先调用基类的构造函数,析构函数则相反。因为派生类需要用到基类的成员,所以必须先有基类,再有派生类
静态数据成员的构造函数 -> 父类的构造函数 -> 非静态的数据成员的构造函数 -> 自己的构造函数
注意:
无论创建几个对象, 该类的静态成员只构建一次, 所以静态成员的构造函数只调用1次!!!
- Base->Animal->Fish的继承关系
#ifndef __BASE_H__
#define __BASE_H__
#include <stdio.h>
class Base
{
public:
Base()
{
printf("Base 构造函数n");
}
~Base()
{
}
virtual void Test()
{
}
private:
};
#endif
#ifndef __ANIMAL_H__
#define __ANIMAL_H__
#include "Base.h"
class Animal :public Base
{
public:
Animal()
{
printf("Animal 构造函数n");
}
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
{
m_s32Eye = s32Eye;
m_s32Mouth = s32Mouth;
}
private:
int m_s32Eye;
int m_s32Mouth;
int m_s32Weight;
};
#endif
#ifndef __FISH_H__
#define __FISH_H__
#include <stdio.h>
#include "Animal.h"
class Fish : private Animal
{
public:
Fish()
{
printf("Fish 构造函数n");
}
~Fish()
{
}
private:
};
#endi
#include <stdio.h>
#include "Fish.h"
int main(int argc,char *argv[])
{
printf("=========================n");
Fish clsFish;
printf("=========================n");
}
$ ./demo
=========================
Base 构造函数
Animal 构造函数
Fish 构造函数
=========================
- Base->Animal->Fish同时Base->BaseExt->Fish
#ifndef __BASE_EXT_H__
#define __BASE_EXT_H__
#include <stdio.h>
#include "Base.h"
class BaseExt :public Base
{
public:
BaseExt();
~BaseExt();
private:
};
BaseExt::BaseExt()
{
printf("BaseExt 构造函数");
}
BaseExt::~BaseExt()
{
}
#endif
#ifndef __FISH_H__
#define __FISH_H__
#include <stdio.h>
#include "Animal.h"
#include "BaseExt.h"
class Fish : private Animal,BaseExt
{
public:
Fish()
{
printf("Fish 构造函数n");
}
~Fish()
{
}
private:
};
#endi
$ ./demo
=========================
Base 构造函数
Animal 构造函数
Base 构造函数
BaseExt 构造函数Fish 构造函数
=========================
这里Base类的构造函数调用了两次,而且构造函数的调用顺序与继承先后顺序有关
这里再次修改
#include "Base.h"
class Animal :virtual public Base
{
public:
Animal()
{
printf("Animal 构造函数n");
}
Animal(int s32Eye,int s32Mouth,int s32Weight):m_s32Weight(s32Weight)
{
m_s32Eye = s32Eye;
m_s32Mouth = s32Mouth;
}
private:
int m_s32Eye;
int m_s32Mouth;
int m_s32Weight;
};
#endif
class BaseExt :virtual public Base
{
public:
BaseExt();
~BaseExt();
private:
};
BaseExt::BaseExt()
{
printf("BaseExt 构造函数n");
}
BaseExt::~BaseExt()
{
}
#endif
$ ./demo
=========================
Base 构造函数
Animal 构造函数
BaseExt 构造函数
Fish 构造函数
=========================
这里Base构造函数执行了一次
这里将BaseExt继承修改如下,其他不变
class BaseExt : public Base
{
public:
BaseExt();
~BaseExt();
private:
};
BaseExt::BaseExt()
{
printf("BaseExt 构造函数n");
}
BaseExt::~BaseExt()
{
}
#endif
./demo
=========================
Base 构造函数
Animal 构造函数
Base 构造函数
BaseExt 构造函数
Fish 构造函数
=========================
最后
以上就是甜美咖啡豆为你收集整理的深度探索C++对象模型-------构造函数语意学构造函数语意学构造函数构造函数的default与delete关键字构造函数在继承中的调用顺序的全部内容,希望文章能够帮你解决深度探索C++对象模型-------构造函数语意学构造函数语意学构造函数构造函数的default与delete关键字构造函数在继承中的调用顺序所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复