• 1.非类型模板参数 2.模板的特化 3.继承讲解


    1.非类型模板参数

    模板参数分类类型形参与非类型形参。
    类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
    非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

    #include
    using namespace std;
    
    #define n=100;
    
    template<class T>
    class stack
    {
    private:
    	T _a[n];
    	int _top;
    };
    
    
    int main()
    {
    	stack<int> st1;//100
    	stack<double> st2;//500
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    看到上面代码我想要开栈开int开100,double开500,但是不行啊,n被定义成了100做不到啊
    但是我们我们可以这样,在模板里面加个参数,到时实例化的时候就是为是我们想开的大小,而这个n就是非类型模板参数

    #include
    using namespace std;
    
    
    template<class T,size_t n>
    class stack
    {
    private:
    	T _a[n];
    	int _top;
    };
    
    
    int main()
    {
    	stack<int,100> st1;//100
    	stack<double,500> st2;//500
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    而这个n上面我也讲了是常量,不能更改
    验证
    可以以看到st1和st2,里面的100和500没被引用
    在这里插入图片描述
    有些类型是不能当非类型模板参数的string和double都不能当,能当的大部分都是整型,char也能当
    在这里插入图片描述

    2.模板的特化

    看我们下面前面二个打印没错,但是为什么第三个就有问题了,7月16怎么可能小于7月15
    在这里插入图片描述
    这个错误其实很简单,他们只是比较了地址了而已,要是我们要求这里不能用来解引用p1和*p2怎么解决
    我们来解决这个问题可以用模板的特化来解决
    函数模板的特化步骤:

    1. 必须要先有一个基础的函数模板
    2. 关键字template后面接一对空的尖括号<>
    3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
    4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
     //针对某些类型要特殊化处理
    template<>
    bool Less<Date*>(Date* left, Date* right)
    {
    	return *left < *right;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    前二个调用的都是正常模板,而第三个我们调用的是特化模板,不过除了这种办法,也可以专门写一个在这个函数的重载来解决
    对于模板特化,函数模板可以避免,但是类模板不行,
    比如我们要一个类模板,一边是int,另一边是double的参数类模板就只能使用特化了
    在这里插入图片描述
    上面图片写的是全特化,除了全特化,还有半特化和偏特化,有点像半缺省
    在这里插入图片描述

    偏特化
    下面的意思是只要是指针就会匹配,如果只有int*,int就走原生版本,必须要求二个指针,要不然就不访问,除了指针,其实还有引用
    在这里插入图片描述

    3.继承

    比如要你写个图书管管理系统,你会发现很多角色有的信息是相同的,比如每个人都有年龄,学生都有学号,地址等等
    这个时候我们就可以用继承来解决,设计一个公共的类,再搞一个私类,把公共的类继承到私类里面
    继承格式
    在这里插入图片描述

    1继承的关系

    在这里插入图片描述
    总结

    1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
    2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
    3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private。
    4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
    5. 在实际运用中一般使用都是public继承,几乎很少使protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强

    2.基类和派生类对象赋值转换

    派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去

    int main()
    {
    	Person p;
    	Student s;
    	s._name = "张三";
    	s._age = 18;
    	s._sex = "男";
    
    	// 子类对象给父类 对象/指针/引用 -- 语法天然支持,没有类型转换 
    	p = s;
    	Person& rp = s;
    	Person* ptrp = &s;
    
    	rp._age++;
    	ptrp->_age++;
    	cout << rp._age << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    Person& rp = s;这个相当于子类中父类的别名
    Person* ptrp = &s;这个相当于子类中父类的指针
    如果上面没理解就和下面图一起看了理解,简单来讲,只能调用子类中父类的参数,还要注意子类对象给父类 对象/指针/引用 时没有类型转换
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    如果我们把子类的公有的改成保护,他还支持吗?
    在这里插入图片描述
    可以看到不支持了,因为父类是公有子类是保护,所以变成了保护,而切片过去为什么不支持,也没什么好讲的保护本来就不支持类型转换

    这里还有注意的是父类赋值给子类是不支持的,不过指针可以

    3.继承中的作用域

    1. 在继承体系中基类和派生类都有独立的作用域。
    2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
      也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
    3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
    4. 注意在实际中在继承体系里面最好不要定义同名的成员。

    下列代码会打印什么是111还是999

    // Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
    class Person
    {
    protected:
    	string _name = "小李子"; // 姓名
    	int _num = 111; 	   // 身份证号
    };
    
    class Student : public Person
    {
    public:
    	void Print()
    	{
    		cout << " 姓名:" << _name << endl;
    		cout << " 学号:" << _num << endl;
    	}
    protected:
    	int _num = 999; // 学号
    };
    
    
    int main()
    {
    	Student s1;
    	s1.Print();
    
    	return 0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行结果
    在这里插入图片描述
    可以看到打印的是999,就近原则,有点像局部原则,先在子类找到,就不去父类找了,这个就叫隐藏,但是如果我就像想访问父类的怎么办
    这个时候可以指定作用域
    在这里插入图片描述
    如果是类里面的函数呢?

    class A
    {
    public:
    	void fun()
    	{
    		cout << "A::func()" << endl;
    	}
    };
    class B : public A
    {
    public:
    	void fun()
    	{
    		cout << "B::func()"<< endl;
    	}
    };
    
    int main()
    {
    	B b;
    	b.fun();
    
    	return 0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    其实和前面的成员函数一样
    在这里插入图片描述

    如果想调用父类的呢?
    在这里插入图片描述

    下面A::fun 和 B::fun 的关系是什么
    
    • 1
    class A
    {
    public:
    	void fun()
    	{
    		cout << "A::func()" << endl;
    	}
    };
    class B : public A
    {
    public:
    	void fun(int)
    	{
    		cout << "B::func()"<< endl;
    	}
    };
    
    int main()
    {
    	B b;
    	b.fun();
    
    	return 0;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    一开始如果不知道隐藏的话,大部分人都会讲上面是重载吧!不过上面的关系是隐藏,函数重载有个最重要的要求:必须要求在同一个作用域,同名

    4.派生类的默认成员函数

    “默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

    class Person
    {
    public:
    	Person(const char* name)
    		: _name(name)
    	{
    		cout << "Person()" << endl;
    	}
    
    	Person(const Person& p)
    		: _name(p._name)
    	{
    		cout << "Person(const Person& p)" << endl;
    	}
    
    	~Person()
    	{
    		cout << "~Person()" << endl;
    	}
    protected:
    	string _name; // 姓名
    };
    
    class Student : public Person
    {
    public:
    	Student(const char* name = "", int num = 0)
    				:_num(num)
    		        ,Person(name)
    
    			{
    				cout << "Student(const char* name = "", int num = 0)" << endl;
    			}
    		
    protected:
    	int _num; //学号
    };
    
    
    int main()
    {
    	Student s1;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    运行结果
    在这里插入图片描述

    可以看到我们调用了父类的构造,再调用了子类的构造函数,最后再调用了父类的析构函数
    子类构造函数原则:
    1.调用父类构造函数初始化继承自父类成员
    2.自己再初始化自己的成员 – 规则参考普通类析构、拷贝构造、复制重载也类似

    子类的构造函数和析构

    		// s1 = s3
    	Student& operator=(const Student& s)
    	{
    		if (this != &s)
    		{
    			Person::operator=(s);
    			_num = s._num;
    		}
    		cout << "Student& operator=(const Student& s)" << endl;
    		return *this;
    	}
    	
    	~Student()
    	{
    		Person::~Person();
    		
    		cout << "~Student()" << endl;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里要注意的是Person::operator=(s);不能写成operator=(s);要不然就会栈溢出,为什么这么写也很好理解,先构造父类的早构造子类的,按照这样,析构应该也是这样的,
    代码

    class Person
    {
    public:
    	Person(const char* name)
    		: _name(name)
    	{
    		cout << "Person()" << endl;
    	}
    
    	~Person()
    	{
    		cout << "~Person()" << endl;
    	}
    
    	Person(const Person& p)
    		: _name(p._name)
    	{
    		cout << "Person(const Person& p)" << endl;
    	}
    
    	Person& operator=(const Person& p)
    	{
    		cout << "Person operator=(const Person& p)" << endl;
    		if (this != &p)
    		_name = p._name;
    		
    		return *this;
    	}
    
    
    
    protected:
    	string _name; // 姓名
    };
    
    class Student : public Person
    {
    public:
    	Student(const char* name = "", int num = 0)
    				:_num(num)
    		        ,Person(name)
    
    			{
    				cout << "Student(const char* name = "", int num = 0)" << endl;
    			}
    
    		// s1 = s3
    	Student& operator=(const Student& s)
    	{
    		if (this != &s)
    		{
    			Person::operator=(s);
    			_num = s._num;
    		}
    
    		cout << "Student& operator=(const Student& s)" << endl;
    		
    		return *this;
    	}
    	
    	~Student()
    	{
    		Person::~Person();
    		
    		cout << "~Student()" << endl;
    		cout << endl;
    	}
    
    protected:
    	int _num; //学号
    };
    
    
    int main()
    {
    	Student s1("李四",4);
    	Student s2(s1);
    	Student s3("王五", 2);
    	s1 = s3;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    运行结果
    这里面一共构造了三个函数,前面没有问题,但是后面看到析构子类的析构了三次,但是父类的析构了6次,把写在子类的父类析构给删了,发觉又没问题了,这其实说明了,为了保证析构顺序先子后父,子类构造结束后父类析构会自动析构,所以不需要我们自己调用父类析构
    在这里插入图片描述

    5.复杂的菱形继承及菱形虚拟继承

    单继承:一个子类只有一个直接父类时称这个继承关系为单继承
    多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

    class Person
    {
    public:
    	string _name; // 姓名
    	int _a[10000];
    };
    class Student : public Person
    {
    protected:
    	int _num; //学号
    };
    class Teacher :  public Person
    {
    protected:
    	int _id; // 职工编号
    };
    class Assistant : public Student, public Teacher
    {
    protected:
    	string _majorCourse; // 主修课程
    };
    
    int main()
    {
    	// 这样会有二义性无法明确知道访问的是哪一个
    	Assistant a;
    	a._name = "peter";
    
    	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
    	a.Student::_name = "xxx";
    	a.Teacher::_name = "yyy";
    
    	cout << sizeof(a) << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    上面这个a是多继承 a._name = “peter”;这样子它不知道访问谁的name,会报错
    在这里插入图片描述
    解决也很很简单指定作用域就行了
    在这里插入图片描述

    而数据冗余就没解决了,数据冗余带来的问题是空间浪费,比如person里面的name
    在这里插入图片描述
    不过要是加上virtual(变成虚继承)就可以避免这个问题
    在这里插入图片描述
    浪费少了整整一半

  • 相关阅读:
    CMMI2.0和1.3之间的区别有哪些?
    (附源码)app智能手机的微课程学习系统 毕业设计 100909
    鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    在uniapp中为自定义组件绑定点击事件点击后没有效果
    节省时间的分层测试,到底怎么做?
    学生HTML个人网页作业作品 简单的IT技术个人简历模板html下载 简单个人网页设计作业 静态HTML个人博客主页
    css实现渐变电量效果柱状图
    Azure KeyVault(三)通过 Microsoft.Azure.KeyVault 类库在 .NET Core 上获取 Secrets
    C++基础学习01
    pandas 学习 第15篇:分组 groupby
  • 原文地址:https://blog.csdn.net/li1829146612/article/details/126055232