手机上的网站是怎么做的,承德市兼职网招聘信息,深圳小程序制作流程,哪些网站可以做产品推广目录
一、前言
二、什么是继承 ?
#x1f4a2;继承的概念#x1f4a2;
#x1f4a2;继承的定义#x1f4a2;
#x1f95d;定义格式
#x1f347;继承权限
三、基类与派生类对象的赋值转换
四、继承的作用域
五、派生类中的默认成员函数
#x1f4a2…目录
一、前言
二、什么是继承 ?
继承的概念
继承的定义
定义格式
继承权限
三、基类与派生类对象的赋值转换
四、继承的作用域
五、派生类中的默认成员函数
默认成员函数的调用
构造函数与析构函数
拷贝构造
赋值运算符重载
显示成员函数的调用
构造函数
拷贝构造
赋值运算符重载
析构函数
六、继承与友元
七、继承与静态成员
八、菱形继承 单继承 多继承 菱形继承
概念
现象
九、继承和组合
十、继承的总结和反思
十一、共勉 一、前言 继承 是 面向对象三大特性之一封装、继承、多态所有的面向对象OO语言都具备这三个基本特征封装相关概念已经在《类和对象》系列中介绍过了今天主要学习的是 继承即如何在父类的基础之上构建出各种功能更加丰富的子 二、什么是继承 ? 什么是继承是继承 -- 遗产 还是继承 -- 花呗答案都不是先来看看官方解释 继承inheritance机制是 ----面向对象程序设计使代码可以复用的重要的手段它允许程序员在保持原有基类父类特性的基础上进行扩展增加功能这样产生新的类称为派生类子类 继承的概念 继承相关概念 被继承对象父类 / 基类 base继承方子类 / 派生类 derived
继承的本质 就是 ------------ 复用代码 举个例子 : 假设我现在要设计一个校园管理系统那么肯定会设计很多角色类比如学生、老师、保安、保洁等等之类的。
设计好以后我们发现有些数据和方法是每个角色都有的而有些则是每个角色独有的。
为了复用代码、提高开发效率可以从各种角色中选出共同点组成 基类比如每个 人 都有姓名、年龄、联系方式等基本信息而 教职工 与 学生 的区别就在于 管理与被管理因此可以在 基类 的基础上加一些特殊信息如教职工号 表示 教职工加上 学号 表示学生其他细分角色设计也是如此
这样就可以通过 继承 的方式复用 基类 的代码划分出各种 子类 像上面共同拥有的数据和方法我们可以重新设计一个类Person 然后让 Student 和 Teacher 去继承它如下:
// 大众类 --- 基础属性
class Person
{
public:Person(string name string(), string tell string(), int age int()):_name(name),_tell(tell),_age(age){}void Print(){cout 我的名字是 : _name endl;cout 我的电话是 : _tell endl;cout 我的年龄是 : _age endl;}
protected:string _name; // 姓名string _tell; // 电话int _age; // 年龄
};// 学生类 --- 派生/子属性
class Student : public Person
{
public:Student(int stuId 1578):Person(XAS,123456789,26),_stuId(stuId){cout 我是一个学生 endl;cout 以下是我的个人信息 endl;cout endl;cout 我的学号为 : _stuId endl;}
protected:int _stuId; // 学号
};// 老师类 --- 派生/子属性
class Teacher : public Person
{
public:Teacher(int workId 2024916):Person(xas,987654321,26),_workId(workId){cout 我是一个老师 endl;cout 以下是我的个人信息 endl;cout endl;cout 我的工号为 : _workId endl;}
protected:int _workId; // 工号
};int main()
{Student s;s.Print();cout --------------------- endl;cout --------------------- endl;Teacher t;t.Print();return 0;
}
继承后父类的 Person 的成员(成员函数成员变量)都会变成子类的一部分。这里体现出了Student 和 Teacher 复用了 Person 继承的定义 了解完 继承相关概念 后就可以开始学习 使用继承 了 定义格式
格式如下: Person 是 父类也称作基类; Student 是 子类也称作派生类。
格式为 子类 : 继承方式 父类比如 class a : public b 就表示 a 继承了 b并且还是 公有继承 注Java 中的继承符号为 extern而 C 中为 : 继承权限
继承有权限的概念分别为公有继承public、保护继承protected、私有继承private
没错与 类 中的访问 限定修饰符 一样不过这些符号在这里表示 继承权限 简单回顾下各种限定符的用途 公有 public公开的任何人都可以访问保护 protected保护的只有当前类和子类可以访问私有 private私有的只允许当前类进行访问 权限大小公有 保护 私有 保护 protected 比较特殊只有在 继承 中才能体现它的价值否则与 私有 作用一样 此时我们发现 ---- 访问权限三种 继承权限三种 根据排列组合可以列出以下多种搭配方案
父类成员 / 继承权限publicprotectedprivate父类的 public 成员外部可见子类中可见外部不可见子类中可见外部不可见子类中可见父类的 protected 成员外部不可见子类中可见外部不可见子类中可见外部不可见子类中可见父类的 private 成员都不可见都不可见都不可见
注所谓的--外部--其实就是 子类对象
总结
无论是哪种继承方式父类中的 private 成员始终不可被 [子类 / 外部] 访问当外部试图访问父类成员时依据 min(父类成员权限, 子类继承权限)只有最终权限为 public 时外部才能访问在实际运用中一般使用都是 public 继承几乎很少使用 protetced/private 继承也不提倡使用 protetced/private继承因为 protetced/private 继承下来的成员都只能在派生类的类里面使用实际中扩展维护性不强。 如何证明呢 通过一下的代码我们来验证上面的结论 // 父类
class A
{
public:int _a;
protected:int _b;
private:int _c;
};// 子类
class B : public A
{
public:B(){cout _a endl;cout _b endl;cout _c endl;}
};int main()
{// 外部子类对象B b; b._a;
}
public 继承 protected 继承 private 继承 之所以说 C 的继承机制设计复杂了是因为 protected 和 private 继承时的效果一样 其实 C 中搞这么多种情况9种完全没必要实际使用中最常见到的组合为 public : public 和 protected : public 如何优雅的使用好 ---- 继承权限 对于只想自己类中查看的成员设为 private对于想共享给子类使用的成员设为 protected其他成员都可以设为 public
比如在张三家中张三家的房子面积允许公开家庭存款只限家庭成员共享而个人隐私数据则可以设为私有
class Home
{
public:int area 500; //500 平米的大房子
};class Father : public Home
{
protected:int money 50000; //存款五万
private:int privateMoney 100; //私房钱怎能公开
};class Zhangsan : public Father
{
public:Zhangsan(){cout 我是张三 endl;cout 我知道我家房子有 area 平方米 endl;cout 我也知道我家存款有 money endl;cout 但我不知道我爸爸的私房钱有多少 endl;}
};class Xiaoming
{
public:Xiaoming(){cout 我是小明 endl;cout 我只知道张三家房子有 Home().area 平方米 endl;cout 其他情况我一概不知 endl;}
};int main()
{Zhangsan z;cout endl;Xiaoming x;return 0;
}三、基类与派生类对象的赋值转换 在继承中允许将 子类 对象直接赋值给 父类但不允许 父类 对象赋值给 子类 这其实很好理解儿子以后可以当父亲父亲还可以当儿子吗
并且这种 赋值 是非常自然的编译器直接处理不需要调用 赋值重载 等函数 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。 (1) 子类对象可以赋值给父类对象
//基类
class Person
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};//派生类
class Student : public Person
{
public:int _id;
};int main()
{Person p;Student s;s._name 张三;s._sex 男;s._age 20;s._id 8888;p s; // 子类对象赋值给父类对象return 0;
}通过调式可以看到为什么没有把 id 赋值过去呢? 这里有个形象的说法叫切片或者切割相当于把派生类中父类那部分切来赋值过去如图所示: (2) 子类对象可以赋值给父类指针
//基类
class Person
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};//派生类
class Student : public Person
{
public:int _id;
};int main()
{Student s;s._name 张三;s._sex 男;s._age 20;s._id 8888;Person* p s;return 0;
}可以看到当父类对象是一个指针的时候照样可以赋值过去 子类对象赋值给父类指针切片图 (3) 子类对象可以赋值给父类引用
//基类
class Person
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};//派生类
class Student : public Person
{
public:int _id;
};int main()
{Student s;s._name 张三;s._sex 男;s._age 20;s._id 8888;Person rp s;return 0;
}可以看到当父类对象是一个引用的时候也可以赋值过去 子类对象赋值给父类引用切换图片 (4) 父类对象不能赋值给子类对象
//基类
class Person
{
public:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};//派生类
class Student : public Person
{
public:int _id;
};int main()
{Student s;Person p;s p;return 0;
}编译会报错 四、继承的作用域 在继承体系中 基类 和 派生类 都有独立的作用域如果子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫 隐藏也叫重定义。 代码示例: Student 的 _num 和 Person的 _num 构成隐藏关系可以看出这样代码虽然能跑但是非常容易混淆。
// 基类
class Person
{
protected:string _name Edison; // 姓名int _num 555; // 身份证号
};// 派生类
class Student : public Person
{
public:void Print(){cout 姓名: _name endl;cout 学号: _num endl;}
protected:int _num 888; // 学号
};int main()
{Student s1;s1.Print();return 0;
}运行可以看到访问的是子类中的_num (类似于局部优先的原则) 那么如果我想访问父类中的_num 呢 ? 可以使用基类 :: 基类成员显示的去访问 :
// 基类
class Person
{
protected:string _name Edison; // 姓名int _num 555; // 身份证号
};// 派生类
class Student : public Person
{
public:void Print(){cout 姓名: _name endl;cout 身份证号: Person::_num endl;}
protected:int _num 888; // 学号
};int main()
{Student s1;s1.Print();return 0;
}可以看到此时就是访问的父类中的 _num 还有一点需要注意的是 : 如果是成员函数的隐藏只需要函数名相同就构成隐藏。
// 基类
class A
{
public:void fun(){cout A::func() endl;}
};// 派生类
class B : public A
{
public:void fun(int i){cout B::func() endl;cout func(int i)- i endl;}
};int main()
{B b;b.fun(10);return 0;
}可以看到默认是去调用子类的 fun() 函数因为成员函数满足函数名相同就构成隐藏。 如果想调用父类的 fun() 还是需要指定作用域
// 基类
class A
{
public:void fun(){cout A::func() endl;}
};// 派生类
class B : public A
{
public:void fun(int i){cout B::func() endl;cout func(int i)- i endl;}
};int main()
{B b;b.A::fun();return 0;
}运行可以看到此时就是调用父类中的 fun() 注意 B 中的 fun 和 A 中的 fun 不是构成函数重载而是隐藏 函数重载的要求是在同一作用域里面
另外在实际中在继承体系里面最好不要定义同名的成员。 五、派生类中的默认成员函数 派生类子类也是 类同样会生成 六个默认成员函数用户未定义的情况下 不同于单一的 类子类 是在 父类 的基础之上创建的因此它在进行相关操作时需要为 父类 进行考虑 这里我们只以下面的两个类为基础讨论四类默认成员函数构造函数、拷贝构造、赋值运算符重载、析构函数
class Person
{
public:Person(const std::string name std::string(), const int age 18, const int sex 1): _name(name), _age(age), _sex(sex){std::cout Person() std::endl;}Person(const Person p): _name(p._name), _age(p._age), _sex(p._sex){std::cout Person(const Person p) std::endl;}Person operator (const Person p){std::cout operator (const Person p) std::endl;if (this ! p){_name p._name;}return *this;}~Person(){std::cout ~Person() std::endl;}
protected:std::string _name;int _age 18;int _sex 1;
};class Student : public Person
{
public:
protected:long long _st_id;
};默认成员函数的调用
构造函数与析构函数
int main()
{Student st;return 0;
}output
Person() // s1 的构造
~Person() // s1 的析构说明了派生类的默认构造函数和默认析构函数都会 -----自动调用基类的构造和析构 拷贝构造
int main()
{Student st1;Student st2(st1);return 0;
}output:
Person() // s1 的构造函数
Person(const Person p) // s2 拷贝构造
~Person() // s2 的析构函数
~Person() // s1 的析构函数说明了派生类的默认拷贝构造会自动调用基类的拷贝构造 赋值运算符重载
int main()
{Student st1, st2;st1 st2;return 0;
}output
Person() // s1 的拷贝构造
Person() // s2 的拷贝构造
operator (const Person p) // 赋值重载
~Person() // s2 的析构函数
~Person() // s1 的析构函数说明了派生类的默认赋值运算符重载会自动调用基类的赋值运算符重载 实际上我们可以将派生类的成员分成三部分基类成员、内置类型成员、自定义类型成员 继承相较于我们以前学的类和对象可以说就是多了基类那一部分 当调用派生类的默认成员函数时对于基类成员都会调用对应基类的默认成员函数来处理 显示成员函数的调用
构造函数 当实现派生类的构造函数时就算不显示调用基类的构造系统也会自动调用基类的构造 class Student : public Person
{
public:Student(long long st_id 111): _st_id(st_id){std::cout Student() std::endl;}
protected:long long _st_id;
};
int main()
{Student st;return 0;
}output:
Person()
Student()
~Person()如果需要显示的调用基类的构造函数应该这样写
Student(long long st_id 111): _st_id(st_id), Person(xas, 18)
{std::cout Student() std::endl;
}特别注意 Person(xas, 18)如果放在初始化列表那就使显式调用基类的构造函数如果放在函数体内那就使创建一个基类的匿名对象 拷贝构造 当实现派生类的拷贝构造时如果没有显式调用基类的拷贝构造那么系统就会自动调用基类的构造 class Student : public Person
{
public:Student(long long st_id 111): _st_id(st_id), Person(xas, 18){std::cout Student() std::endl;}Student(const Student s): _st_id(s._st_id){std::cout Student(const Student s) std::endl;}
protected:long long _st_id;
};
int main()
{Student st1;Student st2(st1);return 0;
}output
Person()
Student()
Person() //系统自动调用了基类的构造
Student(const Student s)
~Person()
~Person()也可以像显示调用构造函数一样显式调用基类的拷贝构造
Student(const Student s): Person(s), _st_id(s._st_id)
{std::cout Student(const Student s) std::endl;
}赋值运算符重载 在实现派生类的赋值运算符重载时如果没有显式调用基类的赋值运算符重载系统也不会自动调用基类的赋值运算符重载 class Student : public Person
{
public:Student(long long st_id 111): _st_id(st_id), Person(xas, 18){std::cout Student() std::endl;}Student operator (const Student s){std::cout operator (const Student s) std::endl;if (this ! s){_st_id s._st_id;}return *this;}
protected:long long _st_id;
};
int main()
{Student st1, st2;st1 st2;return 0;
}output:
Person()
Student()
Person()
Student()
operator (const Student s)
~Person()
~Person()因此在实现派生类的赋值运算符重载时必须显示调用基类的赋值运算符重载
Student operator (const Student s)
{std::cout operator (const Student s) std::endl;if (this ! s){Person::operator(s); //由于基类和派生类的赋值运算符重载构成隐藏因此要用 :: 指定类域_st_id s._st_id;}return *this;
}析构函数 在实现派生类的析构函数时不要显式调用基类的析构函数系统会在派生类的析构完成后自动调用基类的析构 class Student : public Person
{
public:Student(long long st_id 111): _st_id(st_id), Person(xas, 18){std::cout Student() std::endl;}~Student(){std::cout ~Student() std::endl;}
protected:long long _st_id;
};int main()
{Student st1;return 0;
}output:
Person()
Student()
~Student()
~Person()和前面的默认成员函数不同在实现派生类的析构时基类的析构不能显式调用这是因为如果显示调用了基类的析构就会导致基类成员的资源先被清理如果此时派生类成员还访问了基类成员指向的资源就就会导致野指针问题因此必须保证析构顺序为先子后父保证数据访问的安全 六、继承与友元 友元关系不能继承也就是说 基类友元不能访问子类私有和保护成员只能访问自己的私有和保护成员。 下面代码中Display 函数是基类 Person 的友元但是 Display 函数不是派生类 Student 的友元也就是说 Display 函数无法访
class Student;class Person
{
public:friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
};class Student : public Person
{
protected:int _stuNum; // 学号
};void Display(const Person p, const Student s)
{cout p._name endl; // 可以访问cout s._stuNum endl; // 无法访问
}int main()
{Person p;Student s;Display(p, s);return 0;
}可以看到运行会报错 如果想让 Display 函数也能够访问派生类Student 的私有和保护成员只需要在派生类Student 当中进行友元声明。
class Student;class Person
{
public:friend void Display(const Person p, const Student s); // 声明Display是Person的友元
protected:string _name; // 姓名
};class Student : public Person
{
public:friend void Display(const Person p, const Student s); // 声明Display是Student的友元
protected:int _stuNum; // 学号
};void Display(const Person p, const Student s)
{cout p._name endl; // 可以访问cout s._stuNum endl; // 可以访问
}int main()
{Person p;Student s;Display(p, s);return 0;
}七、继承与静态成员 如果基类中定义了static 静态成员,则整个继承体系里面只有一个这样的成员。 无论派生出多少个子类,都只有一个 static 成员实例。 下面代码中在基类 Person 当中定义了静态成员变量 _ count, 派生类 Student 和 Graduate 继承了Person, 但是在整个继承体
// 基类
class Person
{
public:Person() { _count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};// 静态成员在类外面定义
int Person::_count 0; // 派生类
class Student : public Person
{
protected:int _stuNum; // 学号
};// 派生类
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};int main()
{Student s1;Student s2;Student s3;Graduate s4;Person s;cout 人数 : Person::_count endl;cout 人数 : Student::_count endl;cout 人数 : s4._count endl;return 0;
}我们定义了5个对象那么每定义一个对象都会去调用一次 _count 打印以后可以看到这几个对象里面的 _count 都是一样的: 同时我们还可以打印一下地址可以看到也是同-个: 总结: 关于父类中的静态成员子类继续下来以后都是同一个类似于“传家宝。 八、菱形继承 单继承 一个子类只有一个直接父类时称这个继承关系为单继承 多继承 一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承 C 支持多继承即支持一个子类继承多个父类使其基础信息更为丰富但凡事都有双面性多继承 在带来巨大便捷性的同时也带来了个巨大的坑菱形继承问题 概念 首先 C 允许出现多继承的情况如下图所示 这样看很正常是吧但如果出现以下这种 重复继承 的情况就比较麻烦了 此时 普通人X 会纠结于使用哪一个 不用吃饭 的属性这对于编译器来说是一件无法处理的事 现象 将上述概念转化为代码观察实际现象 注多继承时只需要在 父类 之后添加 , 号继续增加想要继承的父类 class Person
{
public:string _name; //姓名
};//本科生
class Undergraduate : public Person
{};//研究生
class Postgraduate : public Person
{};//毕业生
class Graduate : public Undergraduate, public Postgraduate
{};int main()
{Graduate g1;g1._name zhangsan;return 0;
}无法编译 原因分析 Undergraduate 中继承了 Person 的 _namePostgraduate 也继承了 Person 的 _name
Graduate 多继承 Undergraduate 、Postgraduate 后同时拥有了两个 _name使用时无法区分 通过监视窗口查看信息 解决方法 想要解决二义性很简单通过 :: 限制访问域即可
Graduate g1;
g1.Undergraduate::_name zhangsan;
cout g1.Undergraduate::_name endl;但这没有从本质上解决问题而且还没有解决数据冗余问题 真正的解决方法虚继承 注虚继承是专门用来解决 菱形继承 问题的与多态中的虚函数没有直接关系 虚继承在菱形继承的腰部继承父类时加上 virtual 关键字修饰被继承的父类 class Person
{
public:string _name; //姓名
};//本科生
class Undergraduate : virtual public Person
{};//研究生
class Postgraduate : virtual public Person
{};//毕业生
class Graduate : public Undergraduate, public Postgraduate
{};int main()
{Graduate g1;g1._name zhangsan;cout g1._name endl;return 0;
}此时可以解决 菱形继承 的 数据冗余 和 二义性 问题 虚继承是如何解决菱形继承问题的 利用 虚基表 将冗余的数据存储起来此时冗余的数据合并为一份原来存储 冗余数据 的位置现在用来存储 虚基表指针
此时无论这个 冗余 的数据存储在何处都能通过 基地址 偏移量 的方式进行访问 九、继承和组合 public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。 而 组合 是一种 has-a 的关系。假设 B 组合了 A,每个 B对象中都有一个 A对象。 举个例子: 轿车和奔驰就构成 is-a 的关系所以可以使用继承。
// 车类
class Car
{
protected:string _colour 黑色; // 颜色string _num 川A66688; // 车牌号
};// 奔驰
class Benz : public Car
{
public:void Drive(){cout 好开-操控 endl;}
};再举个例子:汽车和轮胎之间就是 has-a 的关系它们之间则适合使用组合。
// 轮胎
class Tire {
protected:string _brand Michelin; // 品牌size_t _size 17; // 尺寸};// 汽车
class Car {
protected:string _colour 黑色; // 颜色string _num 川A66688; // 车牌号Tire _t; // 轮胎
};实际项目中更推荐使用 组合 的方式这样可以做到 解耦避免因父类的改动而直接影响到子类 公有继承is-a — 高耦合可以直接使用父类成员组合has-a — 低耦合可以间接使用父类成员 十、继承的总结和反思 1. 很多人说C语法复杂其实多继承就是一个体现。有了多继承就存在菱形继承有了菱形继承就有菱形虚拟继承底层实现就很复杂。所以一般不建议设计出多继承一定不要设计出菱形继承。否则在复杂度及性能上都有问题。 2. 多继承可以认为是C的缺陷之一很多后来的面向对象语言都没有多继承如Java。 十一、共勉 以下就是我对 【C】继承 的理解如果有不懂和发现问题的小伙伴请在评论区说出来哦同时我还会继续更新对 【C】多态 的理解请持续关注我哦