静态成员函数补充
声明是告诉编译器,这里有一个num的名称占用了,
定义的特点,为num开辟空间,要放数据
编译器在链接时把名字和空间绑在一起
类里面声明的静态变量,只能被这个类产生的对象使用,外部函数不能使用
而且这个静态变量是声明为私有的
上面这两个操作也不允许,私有只能被对象的方法访问
静态函数的特点是没有this指针,非静态函数有this指针
看下面这个情况
程序运行结果如下
通过这个程序我们发现:静态成员可以被继承,继承与静态成员,静态成员只有一个,所有对象共享(包括基类对象和派生类对象)
静态成员num在数据区
静态成员分裂般的情况
给出一个模板
template< class T >
导致类里的成员函数也是一个模板
类外初始化静态成员需这样做
告诉编译器,这个num是在这个模板类里面声明的
如果在类内有一个方法,类内声明,类外定义的话
仍然是一个模板类型
模板类型的成员函数可以直接在类里面实现,如果要在类外实现,必须加模板,object<>里的T加进去
如果类里有静态函数,在类内声明,类外定义
必须给模板
声明的时候给static,定义的时候不加static
template<class T>
class Object
{
private:
int value;
static int num; // 声明
public:
Object(int x = 0) :value(x) {
cout << "Object: " << ++num << endl; }
~Object() {
cout << "Object " << num-- << endl; }
int GetValue()const ;
static void Print();
};
template<class T>
int Object<T>::num = 0; // 定义
template<class T>
int Object<T>::GetValue() const {
return value; }
template<class T>
void Object<T>::Print() {
cout << num << end; }
class Base : public Object<Base>
{
public:
Base(int x) :Object(x)
{
cout << "Base " << endl;
}
~Base() {
}
};
class Test : public Object<Test>
{
public:
Test(int x = 0) :Object(x)
{
cout << "Test " << endl;
}
~Test() {
}
};
int main()
{
Base base1(1);
Base base2(2);
Base base3(3);
Test t1(1);
Test t2(2);
Test t3(3);
return 0;
}
这个情况下程序的运行结果为
这个精神分裂般的情况是如何产生的?
先复习一下模板类
有几个不同的类型,就产生几个不同的实例化代码(在编译时),由编译器实行的
如果说在类里定义静态num
在类内声明一个静态成员
在实例化的时候,不光要产生一个处理int类型的栈,还要把静态成员进行实例化
编译器编译的时候,把静态成员,有几个类型,分裂成几个静态变量
回到主代码中
当编译器编译的时候,发现这是个模板类型,Base继承Object(给出了具体的类型< Base >),系统产生
Test继承Object(< Test >),系统产生
当Base构建对象,里面有一个Object类型的对象,这个Object类型的T类型是Base类型,有自己的num
当Test构建对象,里面有一个Object类型的对象,这个Object类型的T类型是Test类型,有自己的num
不是同一个num哦!
编译器编译的时候对类型实例化过程,造成静态成员num精神分裂
模板类型的T可以是内置类型也可以是自己设计的类型
赋值兼容规则
类型只有公有继承,才体现赋值兼容规则
公有派生!
1、子对象赋值给父对象
2、子对象的地址给父指针
3、子对象初始化父引用
总是把子给父!
什么原因呢?
公有继承代表“是一个”
我们定义一个类型:人
定义一个类型:学生
学生公有继承人
给出一个人对象
给出一个学生对象,学生对象包含一个人
可以说学生是一个人。
所以在这里可以把子对象赋值给父对象
class Person
{
string id;
string name;
public:
Person(string i, string na) :id(i), name(na) {
}
~Person() {
}
};
class String
{
char* str;
public:
};
class Student : public Person
{
int school;
public:
Student(string id, string na, int s) :Person(id, na), school(s)
{
}
~Student() {
}
void Print() const {
cout << school << endl;
}
};
int main()
{
Person ps("9090001", "yhping");
Student s("090111", "yhp", 10);
Student* sp = &s;
sp = (Student*) &ps;
sp->Print();
return 0;
}
ps=s;可以把s对象给给ps,此时发生
切片情况
何为切片情况?
构建ps对象,有id号,姓名
构建s对象,由两部分构成,第一部分是school,第二部分是隐藏的父对象(id号,姓名)
当要把s对象给给ps时,不可能把s的所有空间放过去。只把隐藏的父对象给ps对象赋值。
以上这种情况就是切片情况
子对象base占8个字节 有value,num
obj对象占4个字节,有value
子对象能赋值父对象的原因是公有继承,“是一个”的概念
在物理上,内存操作上面,发生切片现象
父不是子,人不是学生,不能把父对象赋值给子对象
而且就算父给子,只能确定隐藏父对象的值,而子的值不确定
创建子对象的隐藏父对象不是ps哦
可以使用父对象的指针指向父对象,指向子对象都没有问题
string大小是28字节
当程序编译的过程中,给sp开辟空间,有id和姓名,这两个字符串
对象大小与字符串大小无关(字符串存在堆区)
构建s对象,3个28字节,有学校,隐藏父对象有id,姓名
定义一个人的指针,人的指针可以指向人,也可以指向学生的地址
sp指向学生的时候,只能识别隐藏的父对象这一部分。
当你拿父指针指向父对象,可以看见整个父,指向子对象,只能看见隐藏的父对象
引用子对象,也只能引用子对象的隐藏父对象
不能把父对象的地址给子类型的指针
进行强制转换
这个打印Print会出现什么问题呢?
产生崩溃!
我们拿string定义school
sp对象在内存中开辟空间,上面是id号,下面是姓名,我们拿子指针指向父对象的地址,sp是子类型,调动Print,编译器可以编译通过,调动方法,传递指针sp,sp指向对象,调动这个对象的school,但是这个对象没有school,字符串是在堆区开辟
如果是int school;把整型值认为是school,打印出来的就是乱码了
对一个意想不到的空间进行初始化
崩溃还是随机值取决于你定义的是内置类型还是自己设计的类型
继承关系中,构造函数,拷贝构造函数,赋值语句的重载,析构函数之间的关系
这里有一个注意点,endl
当我们要构建base1,给base1开辟足够的空间,大小是4+4=8字节
先调用base的构造函数。先去构建隐藏父对象,Object的构造,Object构造后然后再构造base本身,再初始化num。
先有父再有子
打印的地址是同一个的地址:构建base,空间大小8个字节,包含隐藏父对象,从最上开始就算子对象的地址
析构的时候先把子析构,再把父析构
如果拿base1去构建base2时
构建base1,base1的隐藏父对象,value值,拿Object(x)直接给,等于10,num=x+10=20;
给base2开辟空间(8个字节),要调动拷贝构造函数,base2的隐藏父对象的value和自己的num值各是多少呢?
调动拷贝构造函数,base合成一个拷贝构造函数,合成的拷贝构造函数会不会去调动Object的拷贝构造函数?如果不能调动,将导致value为0,num为20
运行结果是10,20,缺省拷贝构造函数会把base1地址提出来,把base2地址提出来,把base1的值按位拷贝给base2
如果是父对象给出拷贝构造函数,子对象不给出拷贝构造函数
如果是父对象不给拷贝构造函数,子对象给出拷贝构造函数
1、在继承关系中,如果父类没有写拷贝构造函数,子类型也没有写拷贝构造函数
当我们拿子对象构建另一个子对象的时候,把base1的地址抓住,把base2的地址抓住,按位拷贝,一个字节一个字节拷贝进去
2、如果写了父对象的拷贝构造函数,子对象没有写拷贝构造函数,仍然能合成一个拷贝构造函数,可以把base1内容完全拷贝给base2
3、如果子类型写了拷贝构造函数,父类型也写拷贝构造函数,拷贝构造的维护就交给程序人员,很麻烦:当base1构建base2时,调动base的拷贝构造函数,先构建父对象,系统默认调动缺省构造函数而没有调动父对象的拷贝构造函数,要明确告知,调动父类的拷贝构造函数
如果设计一个类型的时候,并没有动态申请空间,不需要外部资源,就不要写拷贝构造函数,系统的拷贝构造函数足以应付
如果需要外部资源,空间,就要把父子类的拷贝构造函数写进去,并且明确告知子类的拷贝构造要调动父类的拷贝构造函数
缺省赋值,把base2 和 base1的地址抓住,按位赋值
父类型写赋值语句重载,子类型不写赋值语句重载
子类型合成了一个赋值语句,可以调动父的
如果父没有赋值语句重载,子写了赋值语句重载
赋值都可以进行。但是父类型没有写赋值语句重载
如果父类型写了赋值语句重载,子类型写了赋值语句重载
必须显示说明子的赋值语句调动父的赋值语句,如果没有显示,没有办法调动父对象的赋值语句
如果不是继承关系,是包含关系(组合关系)
只有构造函数,析构函数
我们都有相应的构造函数,首先构造base1,先构造成员,
如果没有
会不会构造obj对象?和 num
会!调动缺省构造函数,把obj对象构建出来,拿x赋值,要创建临时对象对obj赋值,然后销毁临时对象
如果在列表调动构造函数,构造obj的同时就初始化了
参数列表效率高!
如果父类型有一个拷贝构造函数,那么子类型的拷贝构造函数会不会去调用父类型的拷贝构造函数呢?
会的,合成了成员拷贝构造函数。
如果写了子的拷贝构造函数,没有写父的拷贝构造函数,能不能反向要求父的拷贝构造函数?
如果都写了,也不调动父的拷贝构造函数
要写
继承关系是是一个,而这种关系是由什么构成,区别在于构造
#include<iostream>
#include<string>
using namespace std;
// construct, copy construct , opeator = , deconstruct
class Object
{
int value;
public:
Object(int x = 0) :value(x) {
cout << "construct object: " << this << endl;
}
Object(const Object& obj) :value(obj.value)
{
cout << "copy construct object: " << this << endl;
}
/*Object& operator=(const Object& obj)
{
if (this != &obj)
{
value = obj.value;
}
cout << "Object = " << endl;
return *this;
}
*/
~Object()
{
cout << "deconstruct object: " << this << endl;
}
};
class Base
{
private:
Object obj;
int num;
public:
Base(int x = 0) :num(x + 10), obj(x)
{
//obj = x;
cout << "construct Base: " << this << endl;
}
~Base()
{
cout << "deconstruct Base: " << this << endl;
}
Base(const Base& base) :num(base.num) ,obj(base.obj)
{
cout << "copy construct Base: " << this << endl;
}
/*
Base& operator=(const Base& base)
{
if (this != &base)
{
Object::operator=(base); //
num = base.num;
}
cout << "Base = " << endl;
return *this;
}
*/
};
int main()
{
Base base1(10);
Base base2(base1);
return 0;
}
class Object
{
int value;
public:
Object(int x = 0) :value(x) {
cout << "construct object: " << this << endl;
}
Object(const Object& obj) :value(obj.value)
{
cout << "copy construct object: " << this << endl;
}
Object& operator=(const Object& obj)
{
if (this != &obj)
{
value = obj.value;
}
cout << "Object = " << endl;
return *this;
}
~Object()
{
cout << "deconstruct object: " << this << endl;
}
};
class Base : public Object
{
private:
int num;
public:
Base(int x = 0) :num(x + 10), Object(x)
{
cout << "construct Base: " << this << endl;
}
Base(const Base& base) :num(base.num),Object(base) //
{
cout << "copy construct Base: " << this << endl;
}
~Base()
{
cout << "deconstruct Base: " << this << endl;
}
Base & operator=(const Base &base)
{
if (this != &base)
{
Object::operator=(base); //
num = base.num;
}
cout << "Base = " << endl;
return *this;
}
};
int main()
{
Base base1(10); // sizeof(Base); 8 bytes;
Base base2(100);
base1 = base2;
return 0;
}
多态和虚函数
早起绑定
名字和地址的关联,编译时关联的,早期绑定
运行时多态,必须是继承,公有继承,虚函数
比喻和类推