目录
模版收尾
继承
赋值兼容转换
继承的作用域
同名变量
同名成员函数
派生类的成员构造函数
不写构造函数
写构造函数
不写拷贝构造函数
手动写拷贝构造
运算符重载
析构函数
模版的声明和定义不能分离,否则会报错.
写下面三个文件:
Stack.h
#pragma once
#include
using namespace std;
template
T Add(const T& left, const T& right);
Stack.cpp
#include"Stack.h"
template
T Add(const T& left, const T& right)
{
cout << 1 << endl;
return left + right;
}
Test.cpp
#include"Stack.h"
int main()
{
Add(1,2);
fun();
return 0;
}
运行结果:
如上图所示,模版的声明和定义分离会出现链接错误。
如果我们正常函数的定义和分离就不会出错,比如下图fun函数:
这是刚开始我们把代码加载进内存,运行之后先执行预处理,预处理把"Stack.h"展开。
展开之后就会在Stack.cpp里面找,但是Stack.cpp里面并没有对模版进行实例化,也就找不到地址:
为什么Add预处理阶段找不到地址呢?这是因为在Stack.cpp也就是定义里面Add没有被实例化:
Add是模版,没有实例化,在预编译阶段找不到地址,但是fun我们一开始就定义了是void类型,所以不管是预处理,编译,链接阶段都可以找到它的地址。
解决方案:
1.显示实例化:
Add(1.0, 2.0);
就会报错:
1>Test.obj : error LNK2019: 无法解析的外部符号 "double __cdecl Add(double const &,double const &)" (??$Add@N@@YANABN0@Z),函数 _main 中引用了该符号
需要再加个类型显示实例化:
template
double Add(const double& left, const double& right);
运行:
1
1
但是这样太麻烦了,干脆不要声明定义分离了,把Stack.cpp删掉,定义全部放到Stack.h里面:
继承就是一种复用:
举例:
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18;
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
//Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
//以看到变量的复用。调用Print可以看到成员函数的复用。
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
name:peter
age:18
name:peter
age:18
如上图,teach类和student类都继承了person类的成员函数和成员变量
如果继承方式不写,class默认是私有,struct默认是公有:
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18;
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
//Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
//以看到变量的复用。调用Print可以看到成员函数的复用。
class Student : Person
{
protected:
int _stuid; // 学号
};
class Teacher : Person
{
protected:
int _jobid; // 工号
};
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
不同类型的相近对象之间赋值的时候会进行隐式类型转化,隐式类型转换会产生临时变量。
如:
double d = 2.2;
int& r = d;
报错原因:
int 和double是相近类型,因为都表示大小,把double d赋值给int i,那么r对int d取地址实际上不是对d取地址,而是对临时变量取地址。
解决方法:
临时变量具有常性,加个const就行了:
const int& r = d;
那子类可不可以给父类呢?
class Person{};
class Student : public Person{};
int main()
{
Student a;
Person b = a;
return 0;
}
没有报错可以的,因为父子之间是一种很亲近的关系。
在public继承下,父子之间是一种 is a关系:
student is a person ,teacher is a person:
那既然是很亲近的关系,会进行隐式类型转换,那么肯定也会产生临时对象:
Student a;
Person& b = a;
没加const没有报错。难道自定义类型不会隐式类型转换吗?
下面代码可证明自定义类型也可以隐式类型转换:
string c = "xxxx";
string& d = "xxxx";
string c = "xxxx";
const string& d = "xxxx";
原因:
如下代码,父类和子类都有number变量,此时访问子类,打印出来的是子类的number:
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; // 学号
};
void Test()
{
Student s1;
s1.Print();
};
int main()
{
Test();
return 0;
}
姓名:小李子
身份证号:999
如果想访问父类的number,需要突破作用域:
class Person
{
protected:
string _name = "小李子"; // 姓名
int _num = 111;//身份证号
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 身份证号:" << Person::_num << endl;
}
protected:
int _num = 999; // 学号
};
void Test()
{
Student s1;
s1.Print();
};
int main()
{
Test();
return 0;
}
姓名:小李子
身份证号:111
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
cout << "func(int i)->" << i << endl;
}
};
void Test()
{
B b;
b.fun(10);
};
int main()
{
Test();
return 0;
}
答案:B
//会调用子类同名成员函数,隐藏父类同名成员函数
运行结果:func(int i)->10
如果我现在不带参:
void Test()
{
B b;
b.fun();
};
假设我就是不想带参,那就调父类嘛,突破一下作用域:
void Test()
{
B b;
b.A::fun();
};
运行结果:func()
自定义类型,如果我们不写,编译器会自动生成构造函数
派生类如果不写会调用父类的构造函数:
class Person
{
public:
Person()
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
protected:
int _num; //学号
};
int main()
{
Student s1;
return 0;
}
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
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;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name,int age)
:_name()
,_age(age)
{
cout << "student const(student& s)" <
这是因为派生类初始化父类成员变量必须把父类当成一个对象,而不是直接去初始化父类的成员变量,如果派生类不写构造函数会默认调用父类构造函数,如果派生类想写构造函数必须显示调用父类构造函数。
改成如下:
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
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;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name,int age)
:Person(name)
,_age(age)
{
cout << "student const(student& s)" <
子类拷贝默认调用父类的拷贝构造:
Student s1("张三",18);
Student s2(s1);
派生类写拷贝构造要先显示调用父类拷贝构造
Student(const Student& s)
:Person(s)//切片,把父类的成员变量切出来去给父类拷贝构造
,_age(s._age)
{
cout << "Student (const student&)" << endl;
}
Student& operator=(const Student& s)
{
if (&s != this)
{
operator=(s);
_age = s._age;
}
cout << "student operator =(const student& s)" << endl;
return *this;
}
会栈溢出,一值调子类的operator:
这是因为子类隐藏了父类,要想调父类的必须要显示调用:
Student& operator=(const Student& s)
{
if (&s != this)
{
Person::operator=(s);
_age = s._age;
}
cout << "student operator =(const student& s)" << endl;
return *this;
}
子类调用析构不用显示调用父类析构,系统会自动按照先子后父调用析构
~Student()
{
cout << "~Student()" << endl;
}
演示:
在快结束时按fn+f11会调用析构,如下图显示:程序结束时先调用子类析构,再调用父类析构:
如图,子类继承父类的静态变量,我们把地址打印出来看看,发现是同一个地址,也就是并没有继承,而是直接拿来用了。
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()
{
cout << " 人数 :" << &Person::_count << endl;
cout << " 人数 :" << &Person::_count << endl;
return 0;
}
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
/*
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。
虚拟继承解决数据冗余和二义性的原理
为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
员的模型。*/
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";
return 0;
}
解决方法
1:指定:
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
/*
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。
虚拟继承解决数据冗余和二义性的原理
为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
员的模型。*/
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}
class Student :virtual public Person
class Teacher : virtual public Person
class Assistant : virtual public Student, public Teacher
所以选C