面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承是继承不了类的私有数据的。
继承方式有:公有继续,私有继承,受保护继承
如果省略继承方式就是默认为私有继承。和访问修饰符一样,不写就是默认私有。最好不要继承太多父类,占用内存,而且任意函数重复。
只继承别的父类来用就是继承,在父类的基础上进行功能新增和扩展就是派生。
多继承:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
其中,访问修饰符继承方式是 public、protected 或 private 其中的一个,用来修饰每个基类,各个基类之间用逗号分隔。
class person
{
private:
int id;
public:
string name;
int age;
person()
{
name = "pyg";
age = 12;
}
void test()
{
cout << "from father class" << endl;
}
};
//访问属性就是在子类里的数据形式,如果继承一个父类是单继承,继承多个父类叫多继承
class stu:public person
{
public:
//子类可以新增功能
void learn()
{
cout << "我爱学习" << endl;
}
};
int main()
{
//通过继承子类可以使用父类在所包含的成员,子类不需要从0做起,可以用父类的数据
stu s1;
s1.test();//from father class
s1.learn();//我爱学习
cout << sizeof(s1) << endl;//父类的私有成员虽然用不了,但是会占内存,所以不要继承太多类
return 0;
}
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
一个派生类继承了所有的基类方法,但下列情况除外:
基类的构造函数、析构函数和拷贝构造函数。
基类的重载运算符。
基类的友元函数。
类的各成员是否可被使用:私有只有自己能用
派生类经历三个步骤:
吸收基类成员
改造基类成员
添加新的成员
派生类对象在创建时,会触发父类的构造函数
如果父类没有默认构造又有构造函数,也是要给父类传参的。
class person
{
private:
int id;
public:
string name;
int age;
person(string name,int age)
{
this->name = name;
this->age = age;
cout << "person struct" << endl;
}
/*person()
{
cout << "person struct" << endl;
}*/
void test()
{
cout << "from father class" << endl;
}
};
//访问属性就是在子类里的数据形式,如果继承一个父类是单继承,继承多个父类叫多继承
class stu:public person
{
int sorce;
public:
//子类可以新增功能
void learn()
{
cout << "我爱学习" << endl;
}
//父类没有默认构造,派生类构造函数要把数据传给父类,也要给自己传参初始化
//初始化列表也可以给父类的构造初始化,进行传参
stu(string name,int age,int sorce):person(name,age)
{
this->sorce = sorce;
cout << "派生类构造" << endl;
}
void print()
{
cout << name << " " << age << " " << sorce << endl;
}
};
int main()
{
//通过继承子类可以使用父类在所包含的成员,子类不需要从0做起,可以用父类的数据
//创建派生类对象时,先调父类的构造函数,在创建的时候给stu类型传参不行,要通过person类型传参
stu s1("pyg",25,99);
s1.test();//from father class
s1.learn();//我爱学习
cout << sizeof(s1) << endl;//父类的私有成员虽然用不了,但是会占内存,所以不要继承太多类
s1.print();
return 0;
}
如果父类没有默认构造要传参,就要在派生类构造的初始化列表传给父类。
一般在类内声明不用加初始化列表,在类外时才加:
类内:
stu(string name, int age, int sorce);
类外:
stu::stu(string name, int age, int sorce) :person(name, age)
{
this->sorce = sorce;
cout << "派生类构造" << endl;
}
//string===数值类型
// int,float double -> string to_string()
string str1= to_string(12);
string str2 = to_string(52.3f);
cout << str1 << str2 << endl;
//string -> int double stoi stof
string temp = "12";
cout << 12 + stoi(temp) << endl;//
//在派生类对象销毁时 析构的调用顺序
class father
{
public:
father()
{
cout << "我是father的构造函数" << endl;
}
~father()
{
cout << "我是father的析构函数" << endl;
}
};
class mother
{
public:
mother()
{
cout << "我是mother的构造函数" << endl;
}
~mother()
{
cout << "我是mother的析构函数" << endl;
}
};
//当派生类继承多个父类,此时派生类对象在删除时,对于父类的析构调用顺序与构造的调用顺序刚好相反。
class son :public father,public mother
{
public:
son()
{
cout << "son的构造函数" << endl;
}
~son()
{
cout << "son的析构函数" << endl;
}
};
int main()
{
son myson; //派生类在构建对象时,先调用父类的构造,再调用派生类的构造函数,当派生类对象销毁时,先调用派生类的析构最后调用父类的析构
return 0;
}
//我是father的构造函数
//我是mother的构造函数
//son的构造函数
//
//son的析构函数
//我是mother的析构函数
//我是father的析构函数
//子类的成员可以和父类中成员出现重名
class father
{
public:
father()
{
cout << "我是father的构造函数" << endl;
}
void test()
{
cout << "from father 中的method" << endl;
}
~father()
{
cout << "我是father的析构函数" << endl;
}
};
//当派生类继承多个父类,此时派生类对象在删除时,对于父类的析构调用顺序与构造的调用顺序刚好相反。
class son :public father
{
public:
son()
{
cout << "son的构造函数" << endl;
}
void test()
{
cout << "from son 中的method" << endl;
}
~son()
{
cout << "son的析构函数" << endl;
}
};
int main()
{
son myson;
myson.test();//当派生类中的成员与父类中的成员如果出现重名,此时派生类对象在调用该同名成员时,优先调用自身的同名成员
return 0;
}
class father
{
public:
father()
{
cout << "我是father的构造函数" << endl;
}
void test()
{
cout << "from father 中的method" << endl;
}
~father()
{
cout << "我是father的析构函数" << endl;
}
};
class mother
{
public:
mother()
{
cout << "我是mother的构造函数" << endl;
}
void test()
{
cout << "from mother 中的method" << endl;
}
~mother()
{
cout << "我是mother的析构函数" << endl;
}
};
//当派生类继承多个父类,此时派生类对象在删除时,对于父类的析构调用顺序与构造的调用顺序刚好相反。
class son :public father,public mother
{
public:
son()
{
cout << "son的构造函数" << endl;
}
void test()
{
cout << "from son 中的method" << endl;
}
~son()
{
cout << "son的析构函数" << endl;
}
};
int main()
{
son myson;
myson.father::test();//二义性问题多发生在多继承的场景下,当多个父类中出现相同的成员函数,此时派生类对象在调用该同名成员函数时出现访问的不确定性
myson.mother::test();//1.出现二义性问题的解决方法: 派生类对象在调用同名成员时,在成员名前加上父类名做限定
myson.test(); //2.可以在派生类中对父类的同名方法进行重新实现,这样 派生类对象在调用同名成员,会优先调用派生类的
return 0;
}
//我是father的构造函数
//我是mother的构造函数
//son的构造函数
//from father 中的method
//from mother 中的method
//from son 中的method
//son的析构函数
//我是mother的析构函数
//我是father的析构函数
//菱形继承-----危害:数据冗余(占用过多的内存),
//造成底层类对象 在访问最顶层类的数据成员时,出现访问的不确定性
class base
{
public:
int a;
int b;
int c;
int d;
};
//对于菱形继承的场景 需要中间类采用虚继承 通过虚继承,可以让底层类d 只获取一份a 的数据成员
class b:virtual public base
{
};
class c :virtual public base
{
};
class d :public b, public c
{
};
int main()
{
d dd;
cout << sizeof(dd) << endl;//8 说明了 dd对象存放了两份Base中的数据成员
dd.a;
return 0;
}
不同类型数据间在一定条件下可以进行类型的转换。这种不同类型之间的转换和赋值称为赋值兼容。基类与派生类型对象之间也有赋值兼容关系,因为派生类中包含从基类继承的成员,因此可以将派生类的值赋值给基类对象
class base
{
public:
int a;
base()
{
a = 10;
}
};
class child :public base
{
public:
int c;
child()
{
c = 0;
}
};
int main()
{
base a;
child c;
a = c;//派生类对象可以给基类对象进行赋值,因为赋值的兼容性--父类类型兼容子类类型
// c = a;//不可以将基类对象给派生类对象赋值。
base* pt = &c;//也可以通过基类指针或基类引用来绑定派生类对象
return 0;
}
class Date
{
public:
int year;
int month;
Date(int y,int m)
{
this->year = y;
this->month = m;
}
void print()
{
cout << year << month;
}
};
class stu
{
public:
string name;
Date bir;//用另一个类的对象做为当前类中的数据成员,这个就叫做类的组合
stu(string n, int y, int m) :bir(y, m)
{
name = n;
}
void test()
{
bir.print();//通过类的组合也可以实现对另一个类代码的复用
cout << bir.month;
}
};
int main()
{
return 0;
}