目录
?
继承:? 子类继承于父类。?
派生:? 父类派生出子类。
我们平时创建的类,如果两个类之间存在某种关系,或者说几个类之间具有一些共同的函数或者变量。那么就可以考虑将共同部分写成父类,使用这些函数和变量的类直接从父类继承就可以,避免写重复的代码。?
比如:写一个男孩类和一个女孩类,对于男孩和女孩,他们都有吃喝玩乐的方法,姓名,年龄等成员。这时候,可以将这些公有的部分设置成一个人类,让男孩和女孩类直接继承。?
如果我们之前写的类,需要更新新的功能,我们不需要去修改源码,直接继承之前那个类然后在子类中实现新的功能就可以了?
就是修改之前写的没用继承的代码, 使用继承,提高代码的质量。
Father类:
class Father {
public:
Father();
Father(const char* name, int age);
private:
string name;
int age;
};
Son类:
class Son :public Father {
public:
Son();
};
Son类继承了Father类,class Son : public Father? ,? 在子类的后面加:,然后继承的方式public,然后父类Father。
那么子类到底从父类中继承了什么呢??
Father f("父类", 20);
Son s;
cout << sizeof(f) << endl;
cout << sizeof(s) << endl;
使用上面的Father类和Son类创建对象,计算大小。? 注意:子类中只有无参构造。?
我们定义父类和子类对象,打印出两个对象的大小。 --? 发现两个类的内存大小都是一样的。
?
上面可能还不太直观,我们可以直接查看类的分布?
步骤: 1. 在项目名右键,选择属性? ? 2.? ?选择c/c++,选择命令行? 3. 然后添加?
?/d1 reportSingleClassLayoutSon -- 表示打印单类的布局。 想要打印哪个类的布局就在后面写
哪个类的名字。4. 确定? 5. 编译代码,在输出窗口就能看到相应类的代码了。?
??
上面就是子类Son的布局,
第一行表示,基类(就是父类)为Father, 第二行是成员变name(string类型在64为os中占40个字节),第三行是一个age变量(int 类型占4个字节)。 第四行是内存对齐(4个字节)。??
你会发现,name和age是Father类里面的属性。?
其实子类可以从父类中继承,除父类构造函数和析构函数外的所有成员都会被子类继承。?
那上面为什么没有其它成员函数呢,因为,成员函数并不是和对象存储在一起的。?
为什么不继承构造函数和析构函数:因为构造函数和析构函数的名字是父类的,继承到子类也没用。?
public:?? ? ? ? ?公有权限,在类内,子类内和类外都可以直接访问类中定义的成员?
private:? ? ? 私有权限,只能在类内直接访问,在类外和子类中都无法直接访问,只能使用提供? ? ? ? ? ? ? ? ? ? ? ? ?的public接口简介访问?
protected:?保护权限,可以直接在类内和子类中访问,无法在类外直接访问。
如果有一些数据,希望子类可以直接访问,不希望类外直接访问,那么定义成private就行。
对于private的属性,子类和外界都是一样的,都无法访问。但是,私有属性子类也继承了(前面的例子中),但是,编译器规定,由于这些属性是继承于父类的私有属性,所以在子类的内部是无法直接访问的。(要想访问也得调用父类提供的公共接口)?
对于子类,父类中的private,和public属性都可以在子类中直接访问,但是私有属性不可以。?
先看一段代码:? 我们分别在子类和父类的构造函数中输出__FUNTION__(自己的函数名字),然后在主函数中,创建子类,来看一下子类和父类的构造函数调用情况?
int main(void) {
Son s;
system("pause");
return 0;
}
?
会发现,创建子类对象时,会先调用其父类的构造函数。?
上面说到,要想构造子类对象,就会先调用父类的构造函数,但是构造函数有默认的,有参数的,应该调用哪一个呢??
如果我们没有指定,编译器默认会调用父类的默认构造函数。(你可以在父类中只写有参数的构造,然后在子类构造函数处,就会报错--父类没有默认构造函数)从这一点就可以看出,当调用子类构造函数的时候,并且在未指定的情况下,编译器会默认调用父类的默认构造函数。?
Son::Son(const char* name, int age):Father(name,age){
std::cout << __FUNCTION__ << std::endl;
this->name = name;
this->age = age;
}
我们可以看到,在子类构造函数的右侧 : 父类名(相应构造函数的参数),这样就可以指定当我们调用子类有参构造函数创建子类对象时,?会调用父类的有参构造函数。
当然,我们此处是在有参数构造处指定的,对于其它的构造函数,还是会调用父类的默认构造函数。?
1. 一种解释,这是c++的语法的规定,也符合正常的环境,先有父亲再有儿子。
2. 另一个解释,就是我们一般会将属性设置为私有的,这样,在子类中无法直接访问从父类继承的成员,这时候就无法对这些属性进行初始化或者赋值了,那怎么办?
我们可以调用父类的构造函数来初始化这些数据,因为私有的变量,是可以在本类的函数中访问的。 所以,我们在:后写的,是函数调用,调用父类构造函数。?
3. 所以c++会在调用子类的构造函数的时候调用父类的构造函数,如果不写默认会调用默认构造函数。(前提,这些构造函数必须存在,否则会出错)?
?子类不仅可以继承父类的成员,当然也可以有自己的成员。
class Son :public Father {
public:
Son();
Son(const char* name, int age);
private:
int play;
};
?子类Son中定义了自己的数据成员,int play,当然也可以定义自己的成员函数。
其实类我们可以看做一个作用域,子类作用域是嵌套在父类作用域中的。?
1. 当我们,使用子类访问成员函数或者成员变量的时候,会先从子类的作用域找,没有的话会从父类的作用域中去找。(逐渐往外)?
?2. 当我们在子类中定义和父类之外的同名的变量或者函数的时候,那么就会对外部定义的同名变量和函数进行覆盖,使用子类对象访问的时候,会访问自己的对象和函数。(同名时)?
往往子类有时会对父类的函数进行重写,虽然它们名字相同但是并不是同一片内存(其实编译之后就不同了)。?
我们可以通过查看父类和子类的同名函数的地址来查看是否是同一个函数
C语言中我们学习过指向函数的函数指针,这里同理,定义一个函数指针,指向成员函数,然后就可以查看成员函数的地址。?
语法: 函数返回值? (函数所在类名::*指针变量)? ?=? ?&函数所在类名::函数名?
class Father {
public:
void address();
protected:
int age;
};
class Son :public Father {
public:
void address();
private:
int link;
};
void Father::address() {
void (Father::*p)() = &Father::address;
printf("%p\n",p);
}
void Son::address() {
void (Son:: * p)() = &Son::address;
printf("%p\n", p);
}
int main(void) {
Father f;
Son s;
f.address();
s.address();
system("pause");
return 0;
}
前面继承语法中有一个访问方式public,还有两种,private,protected,这看起来和访问权限很类似,实则是不一样的。?
public继承方式,继承父类的成员时,?不会改变父类成员的访问权限。
private? ->? private? ? ?public -> public? ? protected -> protected?
protected继承方式,继承父类的成员时,会将public转换为protected,其它权限不变?
private ->? private? ? ? ?public -> protected? ? protected -> protected?
provate继承方式,继承父类的成员时,会将所有访问权限都转换为private?
private -> private? ? ? ?public ->? private? ? protected -> private?
虽然继承方式和访问权限都是三个且都是public,private,protected。但是却不相同。?
继承方式可以说是限制外界的。?
对于子类,继承方式无论是什么,在类的内部都可以直接访问父类的public,protected的成员。?
那要那么多继承方式做什么?? --? 是用来限制外界的。
我们在外界定义子类的对象,通过子类对象调用继承的成员,如果是public方式,那就可以调用对应父类的public成员,protected继承和private继承是不可以在类外进行访问的。??