网络营销包括哪些,九江网站优化,字节跳动广告代理商加盟,网站要什么备案#x1f3e0;专栏介绍#xff1a;浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 #x1f3af;每日格言#xff1a;每日努力一点点#xff0c;技术变化看得见。 文章目录 继承的概念及定义继承的概念继承的定义定义格式继承关系与访问限定符 基类和派生类对象赋值转换继… 专栏介绍浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 每日格言每日努力一点点技术变化看得见。 文章目录 继承的概念及定义继承的概念继承的定义定义格式继承关系与访问限定符 基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承与静态成员复杂的菱形继承及菱形虚拟继承 继承的概念及定义
继承的概念
我们生活中也有继承的例子例如小明继承了孙老师傅做拉面的手艺。继承就是一种延续、复用的方式。C为了提高代码的可复用性引入了继承机制概念如下↓↓↓
继承机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类。继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用继承是类设计层次的复用。
继承的定义
定义格式
下图演示的继承的格式其中Person是父类也称作基类Student是子类也称作派生类。
下面给出代码示例下面代码中Student类继承父类Person↓↓↓
#include iostream
using namespace std;class Person
{
public:void Show(){cout _name _age endl;}
protected:string _name jammingpro; //姓名int _age 18; //年龄
};class Student : public Person
{
private:int _stuId;
};int main()
{Person p;Student s;p.Show();s.Show();return 0;
}上面代码中Student继承父类Person的成员成员函数成员变量后这些成员都变成了子类的一部分。这里的Student复用了Person的成员。通过监视窗口可以看到Student中也有自己的_name、_age成员变量。
继承关系与访问限定符
在C的继承机制中包含3种继承方式及3种类访问限定符如下图所示下面将分别介绍它们。
我们在学习类和对象时就已经接触过访问限定符。其中public成员可以在类外访问而protected与private成员不能在类外访问。但这里的protected和private在继承时是有区别的 ●如果父类愿意让自己的成员被外界访问并愿意让子类继承则定义为public的 ●如果父类希望自己的成员不被外界访问而愿意让子类继承则需要定义为protected ●如果父类不希望自己的成员被外界访问、被继承则需要定义为private的。 父类中的访问限定符表示父类愿不愿意让子类继承而继承方式则可以让子类缩小父类成员的访问权限但不能放大父类成员的访问权限。
父类成员/子类继承方式public继承protected继承private继承父类的public成员变为子类的public成员变为子类protected成员变为子类的private成员父类的protected成员变为子类的protected成员变为子类的protected成员变为子类的private成员父类的private成员子类不可见子类不可见子类不可见
总结 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。 基类private成员在派生类中是不能被访问如果基类成员不想在类外直接被访问但需要在派生类中能访问就定义为protected。可以看出保护成员限定符是因继承才出现的。 实际上面的表格我们进行一下总结会发现基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式等于Min{成员在基类的访问限定符继承方式}其中publicprotectedprivate。 使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public不过最好显示的写出继承方式。 在实际运用中一般使用都是public继承几乎很少使用protetced/private继承也不提倡使用protetced/private继承因为protetced/private继承下来的成员都只能在派生类的类里面使用实际中扩展维护性不强。
针对于总结中的第一点父类private成员实际上还是被子类继承了只是子类无法访问下面使用代码验证↓↓↓
#include iostream
using namespace std;class Base
{
private:int _base;
};class Son : public Base
{}int main()
{Son s;return 0;
}基类和派生类对象赋值转换
●派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。 下面给出代码示例↓↓↓
#include iostream
using namespace std;class Person
{
public:Person(){}Person(const string name, const char sex, const int age):_name(name),_sex(sex),_age(age){}
protected:string _name;char _sex;int _age;
};class Student : public Person
{
public:Student(const string name, const char sex, const int age, const int stuId):Person(name, sex, age),_stuId(stuId){}
private:int _stuId;
};int main()
{Student s(Jammingpro, M, 18, 123456);Person p;ps;return 0;
}从监视窗口可以看到Person对象保存了Student对象的父类成员部分而舍弃了子类自有成员这就是切片。
●基类对象不能赋值给派生类对象。基类对象无法用于构造派生类对象也无法使用派生类对象的拷贝赋值函数但可以显示提供派生类赋值给基类的operator实现
★ps由于派生类中的成员函数、成员对象一般情况下都会多于基类如果基类直接赋值给派生类会导致部分成员数值不确定。因此C默认不提供基类赋值给派生类。
●基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针指向派生类对象时才是安全的。
下面代码演示了基类赋值给派生类指针派生类赋值给基类指针↓↓↓
#include iostream
using namespace std;class Person
{
public:Person(){}Person(const string name, const char sex, const int age):_name(name), _sex(sex), _age(age){}string _name;char _sex;int _age;
};class Student : public Person
{
public:Student(const string name, const char sex, const int age, const int stuId):Person(name, sex, age), _stuId(stuId){}int _stuId;
};int main()
{Student s(Jammingpro, M, 18, 123456);Person p(xiaoming, M, 20);Person* p_s s;//安全Student* s_p (Student*) p;//不安全cout s_p-_stuId endl;return 0;
}为什么说Student* s_p (Student*) p;是不安全的呢由于Person对象中没有申请_stuId的空间但在Student*类型看来它认为它指向的对象有_stuId成员。如果用户访问了s_p-_stuId可能会因为内存非法访问而报错。
继承中的作用域
在继承体系中基类和派生类都有独立的作用域。子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏也叫重定义。在子类成员函数中可以使用 基类::基类成员 显示访问需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏而不需要返回值相同或是参数列表相同。注意在实际中在继承体系里面最好不要定义同名的成员。
下面给出派生类成员变量与基类成员变量同名的例子↓↓↓ 下面代码中由于Son类中的成员变量与Base类中的成员变量重名构成了隐藏。如果使用Son s; cout s._name endl;则只能访问到Son对象中的成员变量而无法访问到父类中的_name成员变量。若需要访问父类的_name成员变量则可以使用类型类作用域符号::来访问即s.Base::_name。
#include iostream
using namespace std;class Base
{
public:string _name Jammingpro;
};class Son : public Base
{
public:string _name xiaoming;
};int main()
{Son s;cout s._name endl;cout s.Base::_name endl;return 0;
}下面给出派生类成员函数与基类成员函数同名的例子↓↓↓这里与成员变量同名的情况相同同名成员函数也会构成隐藏关系如果需要访问父类的同名成员函数需要使用类名类作用域运算符::。
#include iostream
using namespace std;class Base
{
public:void Show(){cout I am _Base endl;}
};class Son : public Base
{
public:void Show(){cout I am _Son endl;}
};int main()
{Son s;s.Show();s.Base::Show();return 0;
}★( ఠൠఠ )test下面的两个同名函数函数名相同参数列表不同分别属于基类和派生类它们构成的关系是隐藏还是函数重载呢
#include iostream
using namespace std;class Base
{
public:void print(char ch){cout Base- ch endl;}
};class Son : public Base
{
public:void print(int num){cout Son- num endl;}
};int main()
{Son s;s.print(A);return 0;
}Base中的成员函数比Son中的成员函数更匹配不需要隐式类型转换而这里还是调用Son中的成员函数说明两者构成的关系是隐藏而不是函数重载。这里要注意在继承关系中派生类与基类只要存在同名函数不管参数列表、返回值是否相同都是隐藏关系。
派生类的默认成员函数
6个默认成员函数“默认”的意思就是指我们不写编译器会变我们自动生成一个那么在派生类中这几个成员函数是如何生成的呢
派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数则必须在派生类构造函数的初始化列表阶段显示调用。
基类提供默认构造函数的情况 派生类在构造时会自动调用基类的构造函数↓↓↓
#include iostream
using namespace std;class Base
{
public:Base(){cout Base() is called endl;}
};class Son : public Base
{
public:Son(){cout Son() is called endl;}
};int main()
{Son s;return 0;
}基类没有提供默认构造函数的情况 基类没有提供默认构造时子类必须在初始化参数列表中显示调用基类的构造函数否则会报错。
#include iostream
using namespace std;class Base
{
public:Base(int b):_b(b){cout Base(int b) is called endl;}
private:int _b
};class Son : public Base
{
public:Son(int b):Base(b){cout Son() is called endl;}
};int main()
{Son s(5);return 0;
}派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
#include iostream
using namespace std;class Base
{
public:Base(int b):_b(b){}Base(const Base b){_b b._b;cout Base(const Base b) is called endl;}
private:int _b;
};class Son : public Base
{
public:Son(int s, int b):_s(s),Base(b){}Son(const Son s):Base(s){_s s._s;cout Son(const Son s) is called endl;}
private:int _s;
};int main()
{Son s(55, 66);Son s2(s);return 0;
}如果我们将上面代码中显示调用基类构造函数的代码去掉则会出现如下报错↓↓↓ ★ps对于上面的报错虽然可以通过给基类提供默认构造函数解决但却无法完成子类中的基类成员的拷贝操作。
派生类的operator可以调用基类的operator完成基类的复制这样可以避免代码冗余这里派生类调用基类的operator不是必须的只是因为基类已经实现了该操作派生类不必再重复编写相同内容。
下面代码演示了派生类调用基类operator函数带来的代码简化↓↓↓
#define _CRT_SECURE_NO_WARNINGS 1#include iostream
#include string
#include cstring
using namespace std;class Person
{
public:Person(const string name, const int age, const char gender):_name(name),_age(age),_gender(gender){}Person operator(const Person p){_name p._name;_age p._age;_gender p._gender;return *this;}void Show(){cout My name is _name , I am _age years old, I am a (_gender M ? boy : girl) endl;}
private:string _name;int _age;char _gender;
};class Student : public Person
{
public:Student(const string name, const int age, const char gender, const char* detail):Person(name, age, gender){_detail new char[strlen(detail) 1];strcpy(_detail, detail);}Student operator(const Student s){Person::operator(s);char* detail new char[strlen(s._detail)];return *this;}void Show(){Person::Show();cout My detail infomation is _detail endl;}~Student(){delete[] _detail;}
private:char* _detail;
};int main()
{Student s(Jammingpro, 18, M, He is good at coding);Student copy s;s.Show();
}派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。 派生类对象初始化先调用基类构造再调派生类构造。
下面代码演示了基类与派生类的构造与析构顺序↓↓↓
#include iostream
using namespace std;class Base
{
public:Base(){cout Base() is called endl;}~Base(){cout ~Base() is deleted obj endl;}
};class Son : public Base
{
public:Son(){cout Son is called endl;}~Son(){cout ~Son() is deleted obj endl;}
};int main()
{Son s;return 0;
}编译器会对析构函数名进行特殊处理处理成destrutor()所以父类析构函数不加virtual的情况下子类析构函数和父类析构函数构成隐藏关系。
★ps关于virtual关键字将于多态中讲解
继承与友元
友元关系不能不继承也就是说基类的友元不能访问派生类的私有和保护成员。
下面代码演示了友元关系无法继承↓↓↓
#include iostream
using namespace std;class Base
{friend void print();
private:int _base 88;
};class Son : Base
{
private:int _son 66;
};void print()
{Base b;cout b._base endl;Son s;cout s._son endl;
}int main()
{print();return 0;
}继承与静态成员
基类定义了static静态成员则整个继承体系中只能有一个这样的成员。无论派生出多少多少个子类都只有一个static成员实例。
#include iostream
using namespace std;class Base
{
public:static int val;
};//静态非const成员变量需要在类外初始化
int Base::val 66;class Son : public Base
{
};class GrandSon : Son
{
};int main()
{cout Base::val endl;cout Son::val endl;cout GrandSon::val endl;return 0;
}复杂的菱形继承及菱形虚拟继承
单继承一个子类只有一个直接父类时称这个继承关系为单继承 下面是一份单继承的代码↓↓↓
#include iostream
using namespace std;class Base
{
public:void base_func(){}int _base;
};class Son : public Base
{
public:void son_func(){}int _son;
};class GrandSon : public Son
{
public:void gs_func(){}int _gs;
};int main()
{GrandSon gs;cout gs endl;gs._base 1;cout gs._base endl;gs._son 2;cout gs._son endl;gs._gs 3;cout gs._gs endl;cout endl;Son s;cout s endl;s._base 1;cout s._base endl;s._son 2;cout s._son endl;return 0;
}上述代码调试时通过监视窗口查看结果如下图所示。我们可以发现GrandSon对象保存了其祖先类Son、Base的成员变量。Son对象保存了其基类成员变量。 下图为GrandSon对象的存储情况↓↓↓ 通过分析上面的执行结果可以得出如下结论再单继承中某个类的成员变量放置于类空间最后该成员变量前放置的是直接父类再往上是爷爷类以此类推。类对象的地址与最顶层的祖先的成员变量地址相同。
多继承一个子类有两个或以上直接父类时称这个继承关系为多继承 下面给出多继承的演示代码↓↓↓
#include iostream
using namespace std;class Base1
{
public:void base1_func(){}int _base1;
};class Base2
{
public:void base2_func(){}int _base2;
};class Son : public Base1, public Base2
{
public:void son_func(){}int _son;
};int main()
{Son s;cout s endl;s._base1 1;cout s._base1 endl;s._base2 2;cout s._base2 endl;s._son 3;cout s._son endl;return 0;
}如果Son先继承Base1再继承Base2则会将Base1的成员变量放在前面后继承的Base2的成员变量放在后面。 如果将Son先继承Base2再继承Base1呢
由上面的执行结果可知先继承的基类的成员变量放置于类对象的前面位置后即成的基类的成员变量放置于类对象的后面位置类自身的成员变量放置于最后。
菱形继承菱形继承是多继承的一种特殊情况 下面给出多继承的演示代码↓↓↓
#include iostream
using namespace std;class Share
{
public:void share_func(){}int _share;
};class Base1 : Share
{
public:void base1_func(){}int _base1;
};class Base2 : Share
{
public:void base2_func(){}int _base2;
};class Son : public Base1, public Base2
{
public:void son_func(){}int _son;
};int main()
{Son s;s._base1 1;s._base2 2;s._son 3;return 0;
}在上述代码的监视窗口可以看出菱形继承有数据冗余和二义性的问题。s中继承了两份Share类的成员变量_share。 C中为了避免菱形继承导致的数据冗余和二义性它引入了虚拟继承。虚拟继承可以解决菱形继承的二义性和数据冗余的问题。下面给出修改后的代码引入虚拟继承的代码↓↓↓
#include iostream
using namespace std;class Share
{
public:void share_func(){}int _share;
};class Base1 : virtual public Share
{
public:void base1_func(){}int _base1;
};class Base2 : virtual public Share
{
public:void base2_func(){}int _base2;
};class Son : public Base1, public Base2
{
public:void son_func(){}int _son;
};int main()
{Son s;cout s endl;s._share 0;cout s._share endl;s._base1 1;cout s._base1 endl;s._base2 2;cout s._base2 endl;s._son 3;cout s._son endl;return 0;
}这里是通过了两个指针指向的一张表。这两个指针叫虚基表指针这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的Share。 欢迎进入浅尝C专栏查看更多文章。 如果上述内容有任何问题欢迎在下方留言区指正b(▽)d