12、构造函数和析构函数
自动调用构造函数
struct Date {
int d, m, y;
Date(int dd,int mm,int yy) {
d = dd; m = mm; y = yy;
}
void print() {
cout << y << "-" << m << "-" << d << endl;
}
};
int main()
{
Date day(19,3,2021);//自动调用Date(***)
return 0;
}
但是如果写
Date day;//错误
因为没有默认构造参数,要加上Date(){}才可以,或者Date(int dd = 1,int mm = 3,int yy = 2020)加上默认参数
构造函数有默认值的形式
struct Date {
int d, m, y;
Date(int dd = 1,int mm = 3,int yy = 2020) {
d = dd; m = mm; y = yy;
}
void print() {
cout << y << "-" << m << "-" << d << endl;
}
};
int main()
{
Date day;
day.print();//2020-3-1
return 0;
}
定义一个学生类,名字是char指针类型,初始化可以把n赋值给name吗?
struct student {
char *name;
int age;
student(char* n = "no_name", int a = 15) {
name = n;/**************?/
age = a;
}
};
不能,指针变量不能给指针变量赋值
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
struct student {
char *name;
int age;
student(const char *n= "no_name", int a = 15) {
int len = strlen(n);
name = new char[len+1];//还有一个接受字符,多分配一个
strcpy(name, n);
age = a;
}
};
int main()
{
student stu1;
student stu2("wang");
student stu3("zhang",23);
cout <<"stu1 "<< stu1.age << "\t" << stu1.name << endl;
cout <<"stu2 " << stu2.age << "\t" << stu2.name << endl;
cout <<"stu3 " << stu3.age << "\t" << stu3.name << endl;
return 0;
}
析构函数
假如调用了f()函数,结束的时候stu1,stu2都会自动销毁
最后定义的变量先销毁
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
struct student {
char *name;
int age;
student(const char *n= "no_name", int a = 15) {
int len = strlen(n);
name = new char[len+1];
strcpy(name, n);
age = a;
}
virtual ~student() {
cout << "destructor!" <<name<< endl;
delete[] name;//防止内存泄漏,其他程序无法使用这块内存
}
};
void f()
{
student stu1;
student stu2("wang");
student stu3("zhang",23);
cout <<"stu1 "<< stu1.age << "\t" << stu1.name << endl;
cout <<"stu2 " << stu2.age << "\t" << stu2.name << endl;
cout <<"stu3 " << stu3.age << "\t" << stu3.name << endl;
}
int main() {
f();
//输出结果:
/*
* stu1 15 no_name
* stu2 15 wang
* stu3 23 zhang
* destructor! zhang
* destructor! wang
* destructor! no_name
*/
return 0;
}
13、访问控制与接口
现在我们把struct改为class,发现不能访问该类,为什么呢?
加个public就可以了
class student {
public:
char *name;
int age;
student(const char *n= "no_name", int a = 15) {
int len = strlen(n);
name = new char[len+1];
strcpy(name, n);
age = a;
}
virtual ~student() {
cout << "destructor!" <<name<< endl;
}
};
进一步把name和age改为私有,在public中通过函数就可以访问私有的name和age
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
class student {
private:
char* name;
int age;
public:
student(const char *n= "no_name", int a = 15) {
int len = strlen(n);
name = new char[len+1];
strcpy(name, n);
age = a;
}
char* get_name() {
return name;
}
int get_age() {
return age;
}
virtual ~student() {
cout << "destructor!" <<name<< endl;
}
};
int main()
{
student stu1;
student stu2("wang",23);
cout <<"stu1 " << stu1.get_name() << "\t" << stu1.get_age() << endl;
cout <<"stu2 " << stu2.get_name() << "\t" << stu2.get_age() << endl;
return 0;
}
如果想修改私有的name和age呢?加一个set_name和set_age
set_name需要重新分配空间
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
class student {
private:
char* name;
int age;
public:
student(const char *n= "no_name", int a = 15) {
int len = strlen(n);
name = new char[len+1];
strcpy(name, n);
age = a;
}
char* get_name() {return name;}
int get_age() {return age;}
void set_name(const char* n = "new_name")
{
/********************注意要释放原来的空间*************************/
delete[] name;
int len = strlen(n);
name = new char[len+1];
strcpy(name, n);
}
void set_age(int a) { age = a; }
virtual ~student() {cout << "destructor!" <<name<< endl;}
};
int main()
{
student stu1;
stu1.set_name("xiao");
stu1.set_age(18);
cout <<"stu1: " << stu1.get_name() << "\t" << stu1.get_age() << endl;
return 0;
}
接口:public的公开成员(一般是成员函数)称为这个类的对外接口,外部函数只能通过这些类来访问private等非public的包含内部细节,从而可以封装保护对象
14、拷贝构造函数、赋值运算符
拷贝构造函数:定义一个类对象同时用另一个对象初始化
赋值运算符:一个对象赋值给另一个对象
下列的这两句都是系统自带的,但是可能出问题。
student m(s);
s = k;//赋值运算符
下面的代码调用了几次构造函数?
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
class student {
public:
char* name;
int age;
student(const char *n= "no_name", int a = 0) {
name = new char[100];//name指向这一块动态内存
strcpy(name, n);
age = a;
cout << "申请了100个char动态空间" << endl;
}
virtual ~student()
{
cout << "析构函数 " <<name<< endl;
//delete[] name;//释放动态内存
}
};
int main()
{
student s;
student m(s);//用s初始化m,拷贝构造函数
cout << "m: " << m.name << " " << m.age << endl;
return 0;
}/*输出:
申请了100个char动态空间
m: no_name 0
析构函数
*/
只在定义s的时候调用了构造函数;但是student m(s)的时候调用的拷贝构造函数是编译器帮我们生成的,没有输出构造函数。
最后析构函数要delete[] m.name,硬拷贝,m和s是完全一样的。
再来看赋值运算符的用法
student s;
student k("John", 56);
cout << "k: " << k.name << " " << k.age << endl;
s = k;
cout << "s: " << s.name << " " << s.age << endl;
自己运行一下上面代码,就知道会出很多问题,下面来自己定义拷贝构造函数
student(const student& s) {//这里加const,防止s被改变,拷贝构造函数的参数类型必须是引用
/*
如果拷贝构造函数中的参数不是一个引用,即形如student(const student s),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。
*/
name = new char[100];
strcpy(name, s.name);
age = s.age;
cout << "拷贝构造函数" << endl;
}
//赋值,返回本对象
student& operator =(const student& s) {
name = new char[100];
strcpy(name, s.name);
age = s.age;
cout << "赋值运算符" << endl;
return *this;
}
整体来运行一下,看看结果是什么
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
class student {
public:
char* name;
int age;
student(const char *n= "no_name", int a = 0) {
name = new char[100];//name指向这一块动态内存
strcpy(name, n);
age = a;
cout << "构造函数" << endl;
}
student(const student& s) {
name = new char[100];
strcpy(name, s.name);
age = s.age;
cout << "拷贝构造函数" << endl;
}
student& operator =(const student& s) {
name = new char[100];
strcpy(name, s.name);
age = s.age;
cout << "赋值运算符" << endl;
return *this;
}
virtual ~student()
{
delete[] name;
cout << "析构函数 " << endl;
}
};
int main()
{
student s;//调用一次构造
student k("John", 56);//调用一次构造
cout << "k: " << k.name << " " << k.age << endl;
s = k;//调用一次赋值
cout << "s: " << s.name << " " << s.age << endl;
student m(s);//d调用一次拷贝
cout << "m: " << m.name << " " << m.age << endl;
return 0;
//最后输出三个析构s,k,m
}
15、类体外定义成员函数
格式:调用Date类的print()函数
void Date::print() {
}
16、类模板
和函数模板类似,我们可以把一个类变成模板类
下图代码的功能是定义一个Array类,重载[ ]运算符
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<cstring>
using namespace std;
class Array{
int size;
double* data;
public:
Array(int s) {
size = s;
data = new double[s];
}
//重载一个下标运算符
double& operator[](int i) {
if (i < 0 || i >= size) {
cerr << endl << "Out of bounds" << endl;
exit(EXIT_FAILURE);
}
else return data[i];
}
~Array() {
delete[] data;
}
};
int main()
{
Array t(5);
t[0] = 1;
t[4] = t[0] + 10;
cout << t[4] << endl;
return 0;
}
这个数组有什么问题?只能存放double类的data。
所以引入模板类,把double都换成T,主函数中告诉他,我的数组成员是int,编译器就把T换成int
Array<int> t(5);
完整代码
#define _CRT_SECURE_NO_WARNINGS//windows系统下防止出错误
#include <iostream>
#include<string>
using namespace std;
template<class T>
class Array{
int size;
T* data;
public:
Array(int s=0) {
size = s;
data = new T[s];
}
//重载一个下标运算符
T& operator[](int i) {
if (i < 0 || i >= size) {
cerr << endl << "Out of bounds" << endl;
exit(EXIT_FAILURE);
}
else return data[i];
}
~Array() {
delete[] data;
}
};
int main()
{
int i;
Array<int> t(5);
t[0] = 1;
t[4] = t[0] + 10;
for (i = 0; i < 5; i++) {
cout << t[i] << " ";
}
cout << endl;
//模板string类
Array<string> d(3);
d[0] = "hello";
d[1] = "world";
for (i = 0; i < 3; i++) {
cout << d[i] << endl;
}
return 0;
}
17、别名typedef
a就是int类型
typedef int a;
18、string-vector
string
string是C++标准库中的类型
#include<string>
简单的string输出
#include <iostream>
#include<string>
using namespace std;
int main()
{
string s1;//默认构造函数,没有参数或参数有默认值
s1 = "fuzhi";//赋值运算符
string s2("hello");//普通构造函数
string s3(s1);//拷贝构造函数 string s3 = s1;
cout << "s1 is: " << s1 << endl;
cout << "s2 is: " << s2 << endl;
cout << "s3 is: " << s3 << endl;
return 0;
}//输出 fuzhi,fuzhi,hello
拷贝构造函数的其他形式
string s4("this is a string", 10); //表示从this is a string中取前10个字符构成s4
string s5(s4,6,4); //s5是从s4中下标为6的字符开始,取4个构成s5
string s6(15, '*'); //s6是15个*构成的字符串
string s7(s4.begin(), s4.end()-5 );//s7是从s4.begin() 到 s4.end()-5
string s8 = s4+ "add" + s2; //字符串连接
遍历string,方法有1、下标;2、迭代器
#include <iostream>
#include<string>
using namespace std;
int main()
{
string s="hello";
string w = "world";
s = s + w;
for (int i = 0; i < s.size(); i++) {
cout << i<<" "<< s[i] <<endl;
}
return 0;
}
另一种遍历方法,string中的常量叠加器iterator
int main()
{
string s="hello";
string::const_iterator i;
for (i = s.begin(); i < s.end(); i++) {
cout <<" "<< *i <<endl;
}
return 0;
}
非常量叠代器,可以修改字符
string s="hello";
string::iterator i;
for (i = s.begin(); i < s.end(); i++) {
*i = 'A';
cout <<" "<< *i <<endl;
}
vector
首先头文件引入
#include<vector>
具体怎么用这个类模板呢?
#include <iostream>
#include<vector>
using namespace std;
int main()
{
//定义了一个数据为double型的s数组向量
vector<double> score_stu;
int stu = 5;
//resize重新改编score的大小
score_stu.resize(stu);
//把5个学生的成绩输入
for (vector<double>::size_type i = 0; i < stu; i++) {
cout << "Enter score for student: " << i + 1 << " : " << endl;
cin >> score_stu[i];
}
cout << endl;
//输出刚刚输入的
for (vector<double>::iterator j = score_stu.begin(); j < score_stu.end(); j++) {
cout<< *j<<" ";
}
return 0;
}
输出也可以这样
for (vector<double>::size_type j = 0; j < stu; j++) {
cout<< score_stu[j]<<" ";
}
19、派生类
子类继承父类
#include <iostream>
using namespace std;
class Employee
{
string name;
public:
Employee(string n);
void print();
};
//Manager除了雇员具有的特点外,还有自己特有的功能
class Manager :public Employee {
int level;
public:
Manager(string n, int le = 1);
};
//在类外实现构造函数Employee(string n)
Employee::Employee(string n) :name(n) {//初始化成员列表
//name = n ;
}
//在类外实现构造函数void print()
void Employee::print() {
cout << name << endl;
}
//实现Manager中的构造函数
Manager::Manager(string n,int le): Employee(n),level(le){
}
int main()
{
Manager m("zhang");
Employee e("Li");
m.print();
e.print();
return 0;
}
派生类的构造函数只能描述它自己的成员和其父类的初始式,不能初始化基类的成员
所以下面类外实现Manager的代码是错的
Manager::Manager(string n,int le): name(n),level(le){
} //name是父类私有的
也可以重载Manager::print()函数
#include <iostream>
using namespace std;
class Employee
{
string name;
public:
Employee(string n) :name(n) {};
void print() {
cout << name << endl;
}
};
class Manager :public Employee {
int level;
public:
Manager(string n, int le = 1) : Employee(n), level(le) {};
void print();
};
void Manager::print() {
cout << "Manager: " <<" ";
Employee::print();
}
int main()
{
Manager m("zhang");
Employee e("Li");
m.print();//变化输出:Manger:
e.print();
return 0;
}
定义一个指针也是可以的
int main()
{
Manager m("zhang");
Employee* e = &m;
e->print();
return 0;
}
20、虚函数和多态
如何定义一个Employee的数组
int main()
{
int num=0;
Employee* e[10];//有10个雇员的数组
Employee m("zhang");
Employee n("liu");
e[num++] = &m;
e[num++] = &n;
cout << num << endl;
return 0;
}
虚函数Virtual Functions
指向父类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;
int main()
{
Employee* p;
Manager m("zhang", 2);
Employee e("Liu");
p = &e;
p->print();//liu
p = &m;
p->print();//*******输出zhang
return 0;
}
p->print();输出”zhang“,没有 “Manager: " <<” ";
p是Employee的指针类型,输出了Employee的zhang,而不是Manager中的print: “Manager: " <<” ";
所以把Employee的print()变为虚函数,就能输出Manager: zhang
#include <iostream>
using namespace std;
class Employee
{
string name;
public:
Employee(string n) :name(n) {};
virtual void print() {
cout << name << endl;
}
};
class Manager :public Employee {
int level;
public:
Manager(string n, int le = 1) : Employee(n), level(le) {};
void print();
};
void Manager::print() {
cout << "Manager: " <<" ";
Employee::print();
}
int main()
{
Employee* p;
Manager m("zhang", 2);
Employee e("Liu");
p = &e;
p->print();//liu
p = &m;
p->print();//虽然p是Employee的指针类型,但是它指向了Manager,所以可以输出zhang
return 0;
}
多态
#include <iostream>
using namespace std;
class Employee
{
string name;
public:
Employee(string n) :name(n) {};
virtual void print() {
cout << name << endl;
}
};
class Manager :public Employee {
int level;
public:
Manager(string n, int le = 1) : Employee(n), level(le) {};
void print();
};
void Manager::print() {
cout << "Manager: " <<" ";
Employee::print();
}
int main()
{
Employee* e[10];
int num = 0;//雇员数目
Employee* p;
string name;
int level;
char c;
cout << "M代表经理,其他代表雇员,请输入:" << endl;
while (cin >> c) {
if (c == 'M' || c == 'm') {//‘M/m’表示经理
cout << "请输入经理的姓名和级别: ";
cin >> name >> level;
p = new Manager(name, level);//新建一个经理
e[num++] = p; //把经理放在数组中
}
else if (c == 'E' || c == 'e') {
cout << "请输入雇员的姓名: ";
cin >> name;//否则的话输入雇员
p = new Employee(name);
e[num++] = p;
}
else break;
cout << "请继续输入" << endl;
}
for (int i = 0; i < num; i++) {
e[i]->print();
}
return 0;
}
21、多重继承
当然,我们可以从一个类派生出多个不同的类
class Employee
{
public:
virtual void print() {
cout << "Employee: " << " ";
}
};
class Manager :public Employee {
public:
void print() {
cout << "Manager: " << " ";
}
};
class Secretary :public Employee {
public:
void print() {
cout << "Secretary: " << " ";
}
};
也可以从多个不同的类中派生出一个类:多重派生(Multiple inheritance)
class one{public:
//
};
class two{public:
//
};
class MultipleInheritance :public one,public two {
public:
//
};
22、纯虚函数和抽象类
纯虚函数(pure virtual function)和抽象类(abstract base class)
函数体=0的虚函数成为”纯虚函数“。包含纯虚函数的类称为”抽象类“
virtual const char* speak() = 0;
下面是一个抽象类的定义
#include <iostream>
#include <string>
using namespace std;
class Animal{
private:
string m_name;
public:
Animal(string name):m_name(name) {
}
string getName() {
return m_name;
}
virtual const char* speak() = 0;
};
主函数如何定义一个Animal类呢?Animal a;可以吗?
int main()
{
Animal a;//ERROR!!抽象类不能被实例化
return 0;
}
Cow是派生出来的,也是抽象类,不能实例化
#include <iostream>
#include <string>
using namespace std;
class Animal{
private:
string m_name;
public:
Animal(string name):m_name(name) {
}
string getName() {
return m_name;
}
virtual const char* speak() = 0;
};
class Cow :public Animal {
public:
Cow(string name):Animal(name) {//Cow仍然是一个抽象类
}
};
int main()
{
Animal a("ani");//ERROR!!抽象类不能被实例化
Cow c("niu");//ERROR!!抽象类不能被实例化
return 0;
}
那应该如何定义,Cow类中重新定义纯虚函数即可
class Cow :public Animal {
public:
Cow(string name):Animal(name) {//Cow仍然是一个抽象类
}
virtual const char* speak() = 0 {
return "Moo";
};
};