C++面向对象程序设计教程
前言
全篇使用标题+代码的形式,知识点的介绍写在代码部分的注释里。书籍为《C++面向对象程序设计教程(第四版)》(陈维兴 林小茶 编著)。
第一章为基础概念,过于简单;第七章为输入输出流,个人理解不深,目前使用较少,两章节暂时省略,后期会补充第七章的内容
第2章 C++概述
2.1 进制输出
#include <iostream>
using namespace std;
int main()
{
int x = 25;
cout << hex << x << ' ' << dec << ' ' << oct << x << endl;
//hex十六进制,dec十进制,oct八进制,cout << '\n'与cout << endl作用相同
return 0;
}
2.2 const辨析
#include <iostream>
using namespace std;
int main()
{
const char *name = "zhou";//指向常量的指针
//name[3] = 'a'; 出错,以上的const修饰char,字符数组无法更改
//name = "zhang"; 正确,可以修改指针的指向
char * const name = "chen";//常指针
//这里注意const和*的位置,*在const前面,*本质上是char连带的部分(char *a, *b但才是声明两个指针,*只修饰最近的变量)
//以上const修饰指针
//name[3] = 'a'; 正确,可以修改指针指向的数据
//name = "zhang"; 错误,不能改变指针所指的地址
const char * const name = "chen";//指向常量的常指针
//name[3] = 'a'; 错误,不能改变指针所指地址中的数据
//name = "zhang"; 错误,不能改变指针所指向的地址
return 0;
}
2.3 带有默认参数的函数
#include <iostream>
using namespace std;
//函数声明中,形参带有初始值
//带有初始值的参数,右边不能有没有初始值的形参,即需要
//int special(int x, int y, int z = 2) 正确
//int special(int x, int y = 3, int z) 错误
int special(int x = 5, int y = 3, int z = 2)
{
return x * y * z;
}
int main()
{
//因为函数声明的要求,所以参数省略只能从最右边开始
//special(, , 20) 错误,不能省略实参只能从最右边开始
cout << special(100, 70, 3) << endl;
cout << special(100, 70, 2) << endl;
cout << special(100, 70) << endl;
cout << special(100, 3) << endl;
cout << special(100) << endl;
cout << special(5) << endl;
cout << special() << endl;
return 0;
}
2.4 函数的重载
#include <iostream>
using namespace std;
int square(int i)
{
return i * i;
}
long square(long i)
{
return i * i;
}
double square(double i)
{
return i * i;
}
/*-------------------------------------------------------------*/
int mul(int x, int y)
{
return x * y;
}
int mul(int x, int y, int z)
{
return x * y * z;
}
int main()
{
//重载需要函数参数类型不同,或者参数个数不同,或者二者兼而有之
//参数类型不同
int i = 12;
long l = 1234;
double d = 5.67;
cout << "i * i" << " = " << square(i) << endl;
cout << "l * l" << " = " << square(l) << endl;
cout << "d * d" << " = " << square(d) << endl;
//参数个数不同
int a = 2, b = 3, c = 4;
cout << mul(a, b) << endl;
cout << mul(a, b, c) << endl;
return 0;
}
2.5 作用域运算符
#include <iostream>
using namespace std;
int a = 10;
int main()
{
int a = 25;
//局部变量和全局变量重名,如果希望在局部变量的作用域中使用同名的全局变量,可以在变量前加上::
//此时,::a代表全局变量a, a代表局部变量a
cout << ::a << endl;
cout << a << endl;
return 0;
}
2.6 new与delete
#include <iostream>
using namespace std;
int main()
{
int *ptr;
ptr = new int;
*ptr = 10;
cout << *ptr << endl;
delete ptr;
//new delete与malloc free辨析
//int *p;
// p = (int *)malloc(sizeof(int));
// free(p);
// p = new int; new可以根据类型自动计算需要分配内存大小,无需sizeof,同时能够自动返回正确的指针类型,不需要强制转换
// delete p;
int *p = new int(99); //使用99作为初始值赋给*p,只能初始化简单变量,无法初始化数组
cout << *p;
delete p;
//数组的动态分配和释放
int *s = new int[10];
delete []s; //delete[]指针变量名
return 0;
}
2.7 引用
2.7.1 初识引用
#include <iostream>
using namespace std;
//引用作为函数参数,能够通过改变形参来改变实参,C++主张使用引用而不是指针来改变实参
void swapp(int &m, int &n)
{
int t;
t = m;
m = n;
n = t;
}
int main()
{
int i;
int &j = i; //引用声明时一定要初始化,即表示j是谁的引用
i = 30;
//只需要在声明时写出&,其他时候不需要(这就是比指针方便的地方)
//也只有在声明中才是引用运算符,其他地方都是取址操作符
cout << "i=" << i << " j=" << j << endl;
j = 80;
cout << "i=" << i << " j=" << j << endl;
//变量i和引用j占内存的同一个位置
cout << "The address of i=" << &i << endl;
cout << "The address of j=" << &j << endl;
//不能建立引用的数组
//不能建立引用的引用
//不能建立指向引用的指针
int a = 5, b = 2;
cout << a << ' ' << b << endl;
swapp(a, b);
cout << a << ' ' << b;
return 0;
}
2.7.2 使用引用返回函数值
#include <iostream>
using namespace std;
int &index(int i); //声明函数返回一个整数类型的引用
int a[] = {1, 3, 5, 7, 9};
int main()
{
index(2) = 25; //将函数调用放在复制元素符的左边,等价于将a[2]赋值为25
cout << index(2);
return 0;
}
int &index(int i)
{
return a[i]; //返回一个a[i]的引用,这样函数调用就能够放在左边了
}
2.7.3 加深理解的一个程序
#include <bits/stdc++.h>
using namespace std;
int &f(int &i)
{
i += 10;
return i;
}
int main()
{
int k = 0;
int &m = f(k);
//答案认为m是函数f()的引用
//我自己有不同的看法,我认为m是k的引用
//&i形参首先把i声明为k的引用,然后返回i,那么m就是k的引用i的引用
cout << k << endl;
cout << m << endl;
m = 20;
cout << k << endl;
k = 30;
cout << m << endl;
return 0;
}
第3章 类与对象
3.1 类与对象的基本概念
3.1.1 初识类
#include <iostream>
#include <cmath>
using namespace std;
//默认情况下,类的数据成员或者成员函数都是private
class Complex
{
private:
double real;
double imag;
public:
void init(double r, double i)
{
real = r;
imag = i;
}
double abscomplex()
{
double t;
t = real * real + imag * imag;
return sqrt(t);
}
};
int main()
{
Complex a;
a.init(1.1, 1.2);
cout << a.abscomplex();
return 0;
}
3.1.2 成员函数的三种定义方式
1. 第一种(最常用)
class Point
{
public:
void setpoint(int, int); // 函数声明中可以不带有形参
int getx();
int gety();
private:
int x;
int y;
;
//将函数声明放在类里面,将函数定义放在外面,前面加上 “类名::” 修饰,表示这是属于某个类的函数
void Point::setpoint(int a, int b)
{
x = a;
y = b;
}
int Point::getx()
{
return x;
}
int Point::gety()
{
return y;
}
2. 第二种
class Point
{
//将函数定义直接放在类里面,此时会把这些函数当作内联函数inline处理(隐式定义)
//这种做法能够减少调用函数的开销,提高执行效率,但是却增加了编译后代码的长度
//所以一般只有很简短的成员函数才定义为内联函数
public:
void setpoint(int x, int y)
{
x = a;
y = b;
}
int getx()
{
return x;
}
int gety()
{
return y;
}
private:
int x;
int y;
};
3. 第三种
class Point
{
public:
inline void setpoint(int, int);
inline int getx();
inline int gety();
private:
int x, y;
};
//和第一种、第二种都有类似的地方,和第一种格式相同,但是不同的是,这些函数和第二种一样被当作内联函数来处理
//这种方法显式地说明了这是一个内联函数
//可以函数声明和函数定义都加上inline,也可以只在一个地方加上
//但是使用inline定义内联函数时,必须将类的声明和内联函数的定义放在同一个文件(或者同一个头文件中),否则编译的时候无法进行代码置换
inline void Point::setpoint(int a, int b)
{
x = a;
y = b;
}
inline int Point::getx()
{
return x;
}
inline int Point::gety()
{
return y;
}
3.2 构造函数和析构函数
3.2.1 构造函数
1. 错误的初始化方法
#include <iostream>
using namespace std;
// 错误的给类成员初始化的方法
class Complex
{
double real = 0; //写法错误,类声明中不能给数据成员赋予初始值
double imag = 0; //类不占用空间,不能容纳具体的数据
};
//另一种不是很好的写法
class Complex
{
public:
double real;
double imag;
};
Complex c1 = {1.1, 2.2};
//对于Complex这个类来说,这种类似于结构体的初始化方法能够进行初始化,因为都是公有变量
//但是如果类中有私有变量或者保护变量,那么无法对它们进行初始化
2. 初识构造函数
#include <iostream>
#include <cmath>
using namespace std;
class Complex
{
private:
double real;
double imag;
public:
//定义构造函数,函数名称和类名相同,一般被声明为公有成员
//构造函数是一种特殊的成员函数,主要为对象分配空间,进行初始化
//构造函数名必须和类名相同,可以具有任意类型的参数,但是不能有返回值和返回值类型
//它不需要用户来调用,而是在建立对象时自动执行
//我们也可以在构造函数中加入其他的功能,但是为了保持构造函数的功能清晰,一般不提倡在构造函数中加入和初始化无关的内容
//如果没有给类定义构造函数,编译系统会自动生成一个默认的构造函数
//但是默认的构造函数不带任何参数,函数体是空的,只为对象数据成员开辟空间,不赋初值
//和普通函数一样,写在内部会被当作内联函数
//在构造函数中,对私有数据real和imag赋值
Complex(double r, double i)
{
real = r;
imag = i;
}
double abscomplex()
{
double t;
t = real * real + imag * imag;
return sqrt(t);
}
};
int main()
{
//第一种形式 类名 对象名[(实参表)]
Complex a(1.1, 2.2);
cout << "a's absolute value = " << a.abscomplex() << endl;
//第二种形式 类名 *指针变量名 = new 类名 [(实参表)]
Complex *pa = new Complex(1.1, 2.2);
cout << "a's absolute value = " << pa->abscomplex() << endl;
delete pa;
//或者cout << "a's absolute value = " << (*pa).abscomplex() << endl;
return 0;
}
3. 构造函数的错误辨析
#include <iostream>
using namespace std;
class Location
{
public:
Location(int m, int n)
{
X = m;
X = n;
}
void Init(int initX, int initY)
{
X = initX;
Y = initY;
}
int GetX()
{
return X;
}
int GetY()
{
return Y;
}
private:
int X, Y;
};
int main()
{
Location A3;//报错,类中已经定义了一个带有参数的构造函数
//所以类有且只有一个构造函数,所以初始化时一定要使用这个带有参数的构造函数
A3.Init(785, 980);
cout << A3.GetX() << " " << A3.GetY() << endl;
return 0;
}
4. 不带参数的构造函数
#include <iostream>
#include <cmath>
using namespace std;
class Complex
{
private:
double real;
double imag;
public:
//这样相当于给每个类的实例赋了一个固定的初始值,如果需要更改数据成员的值,可以使用初始化函数
Complex()
{
real = 0;
imag = 0;
}
void init(double r, double i)
{
real = r;
imag = i;
}
double abscomplex()
{
double t;
t = imag * imag + real * real;
return sqrt(t);
}
};
int main()
{
Complex a;
cout << "a's absolute value = " << a.abscomplex() << endl;
a.init(1.1, 2.2);
cout << "a's absolute value = " << a.abscomplex() << endl;
return 0;
}
5. 成员初始化列表对数据成员进行初始化
例子1
//这种方法不在函数体内用赋值语句对数据成员初始化,而是在函数首部实现
class Complex
{
private:
double real;
double imag;
public:
Complex(double r, double i);
};
Complex::Complex (double r, double i) : real(r), imag(i)
{
//构造函数体
}
//别忘了括号,使用成员初始化列表对数据成员进行初始化
//为什么使用成员初始化列表呢?
//C++中某些类型的成员不允许在构造函数中用赋值语句直接赋值。
//例如:用const修饰的数据成员,或者是引用类型的数据成员,不允许直接用赋值语句赋值
//因此,只能用成员初始化列表进行赋值
例子2
#include <iostream>
using namespace std;
//为什么使用成员初始化列表呢?
//C++中某些类型的成员不允许在构造函数中用赋值语句直接赋值。
//例如:用const修饰的数据成员,或者是引用类型的数据成员,不允许直接用赋值语句赋值
//因此,只能用成员初始化列表进行赋值
class A
{
public:
A(int x1) : x(x1), rx(x), pi(3.14)//用成员初始化列表的方式对引用类型的数据成员rx和const修饰的数据成员pi初始化
{}
void print()
{
cout << "x = " << x << " " << "rx = " << rx << " " << "pi = " << pi << endl;
}
private:
int x;
int& rx;
const double pi;
};
int main()
{
A a(10);
a.print();
return 0;
}
例子3
#include <iostream>
using namespace std;
class D
{
public:
D(int i):mem2(i), mem1(mem2 + 1)
{
cout << "mem1 = " << mem1 << endl;
cout << "mem2 = " << mem2 << endl;
}
private:
int mem1;
int mem2;
};
int main()
{
D d(15);
//mem1初始化结果和所设想的错误,原因在于使用成员初始化列表对数据成员进行初始化
//是按照类中的顺序进行初始化,和成员初始化列表的顺序无关
//所以这个例子中首先初始化mem1,因为mem2此时没有初始值,mem1的赋值无法估计
//总结:成员初始化列表的顺序尽量和类中出现的顺序一致
return 0;
}
6. 构造函数的重载
#include <iostream>
using namespace std;
class Date
{
public:
Date();
Date(int y, int m, int d);
void showDate();
private:
int year;
int month;
int day;
};
//第一个构造函数没有参数,对数据成员赋固定的值
Date::Date()
{
year = 2000;
month = 4;
day = 28;
}
//第二个构造函数有三个参数,在函数体中把参数复制给数据成员
Date::Date(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
inline void Date::showDate()
{
cout << year << '.' << month << '.' << day << endl;
}
int main()
{
Date date1;
//使用无参构造时,如上一行一样,date1后面不用加上(),如果加上括号则声明了一个返回值为Date的函数date1
cout << "Date1 output: " << endl;
date1.showDate();
Date date2(2002, 11, 14);
cout << "Date2 output: " << endl;
date2.showDate();
return 0;
}
7. 计时器例子体现构造函数的重载
#include <iostream>
using namespace std;
class Date
{
public:
Date();
Date(int y, int m, int d);
void showDate();
private:
int year;
int month;
int day;
};
//第一个构造函数没有参数,对数据成员赋固定的值
Date::Date()
{
year = 2000;
month = 4;
day = 28;
}
//第二个构造函数有三个参数,在函数体中把参数复制给数据成员
Date::Date(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
inline void Date::showDate()
{
cout << year << '.' << month << '.' << day << endl;
}
int main()
{
Date date1;
//使用无参构造时,如上一行一样,date1后面不用加上(),如果加上括号则声明了一个返回值为Date的函数date1
cout << "Date1 output: " << endl;
date1.showDate();
Date date2(2002, 11, 14);
cout << "Date2 output: " << endl;
date2.showDate();
return 0;
}
8. 带默认参数的构造函数
#include <iostream>
#include <cmath>
using namespace std;
//如果构造函数在类的声明外定义,那么默认参数应该在类内声明构造函数原型时指定
//而不能在类外构造函数定义时指定。因为类的声明是放在头文件中的,用户可以看到,而构造函数的定义是实现类的细节,用户往往是看不到的。
//因此,在声明时指定默认参数,可以保证用户在建立对象时指导怎么使用默认参数
//如果构造函数的全部参数都指定了默认值,则在定义对象时可以指定1个或者多个实参,也可以不给出实参,这时的构造函数也属于默认构造函数
//如果构造函数的全部参数都指定了默认值,不能重载无参数的默认构造函数,否则系统不知道是调用哪个构造参数,因此产生二义性
//如果构造函数的全部参数都指定了默认值,不能再定义重载的构造函数,否则系统不知道是调用哪个构造参数,因此产生二义性
//所以一般也不同时使用构造函数的重载和带有默认参数的构造函数
class Complex
{
public:
//带有默认参数的构造函数
Complex(double r = 0.0, double i = 0.0);
double abscomplex();
private:
double rear;
double imag;
};
Complex::Complex(double r, double i)
{
rear = r;
imag = i;
}
double Complex::abscomplex()
{
double t = 0;
t = rear * rear + imag * imag;
return sqrt(t);
}
int main()
{
Complex S1;
cout << "S1: " << S1.abscomplex() << endl;
Complex S2(1,1);
cout << "S2: " << S2.abscomplex() << endl;
Complex S3(1.1, 2.2);
cout << "S3: " << S3.abscomplex() << endl;
}
3.2.2析构函数
#include <iostream>
#include <cmath>
using namespace std;
//析构函数也是一种特殊的成员函数。它执行和构造函数相反的操作,通常用于执行一些清理任务
//如释放分配给对象的内存空间等,每个类都有一个析构函数
//如果没有显式地为类定义一个析构函数,则编译系统会自动生成一个默认析构函数
//Complex::~Complex()
//{}
//这个默认的析构函数没有任何参数,一般来说,这个默认析构函数就能满足要求
//但是,如果在一个对象撤销之前,需要额外的工作,就应该显式地定义析构函数
//特点:
//1.析构函数名和类名相同,但是前面加上一个~
//2.析构函数不返回任何值。所以定义析构函数时,不能说明类型,甚至说明为void也不行
//3.析构函数没有参数,因此不能被重载,一个类可以有多个构造函数,但是只能有一个析构函数
//4.撤销对象时,编译系统会自动地调用析构函数
//析构函数被调用的情况
//1.主函数结束或者调用exit函数时,对象被撤销
//2.一个对象被定义在一个函数体内,函数调用结束时,该对象会释放,析构函数将被调用
//3.当一个对象是是使用new运算符创建的,在使用delete运算符释放时,析构函数将被调用
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0);
~Complex();
double abscomplex();
private:
double real;
double imag;
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
cout << "Constructor called." << endl;
}
Complex::~Complex()
{
cout << "Destructor called." << endl;
}
double Complex::abscomplex()
{
double t = 0;
t = real * real + imag * imag;
return sqrt(t);
}
int main()
{
Complex A(1.1, 2.2);
cout << "Absolute value: " << A.abscomplex() << endl;
}
3.2.3构造函数和析构函数的常见用法
class String_data
{
public:
String_data(char *s)//构造函数
{
str = new char[strlen(s) + 1];//用运算符new为字符串str动态地分配了一个存储空间
strcpy(str, s);
}
~String_data()//析构函数
{
delete str;//用运算符delete释放动态分配的存储空间
}
void get_info(char *);
void sent_info(char *);
private:
char *str;
};
3.3 对象数组与对象指针
3.3.1 对象数组
1. 对象数组1
#include <iostream>
using namespace std;
class exam
{
public:
exam(int n)
{
x = n;
}
int get_x()
{
return x;
}
private:
int x;
};
int main()
{
//有几个数组元素就要调用几次构造函数。
//如果构造函数只有1个参数,在定义对象数组时就可以直接在等号后面的花括号内提供实参
exam ob1[4] = {11, 22, 33, 44};
for (int i = 0; i < 4; i++)
cout << ob1[i].get_x() << " ";
return 0;
}
2. 对象数组2
#include <iostream>
using namespace std;
class exam
{
public:
exam()
{
x = 123;
}
exam(int n)
{
x = n;
}
int get_x()
{
return x;
}
private:
int x;
};
int main()
{
exam ob1[4] = {11, 22, 33, 44};
//首先调用带一个参数的构造函数初始化ob2[0]和ob2[1],然后调用不带参数的构造函数初始化ob2[2], ob2[3]
exam ob2[4] = {55, 66};
for (int i = 0; i < 4; i++)
cout << ob1[i].get_x() << " ";
cout << endl;
for (int i = 0; i < 4; i++)
cout << ob2[i].get_x() << ' ';
return 0;
}
3. 对象数组3
#include <iostream>
#include <cmath>
using namespace std;
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i)
{}
~Complex()
{
cout << "Destructor called." << endl;
}
double abscomplex()
{
double t;
t = real * real + imag * imag;
return sqrt(t);
}
private:
double real, imag;
};
int main()
{
Complex com[3] = {Complex(1.1, 2.2), Complex(3.3, 4.4), Complex(5.5, 6.6)};
cout << com[0].abscomplex() << ' ' << com[1].abscomplex() << ' ' << com[2].abscomplex() << endl;
}
3.3.2 对象指针
1. 使用指针访问单个对象成员
#include <iostream>
using namespace std;
class exe
{
public:
void set_a(int x)
{
a = x;
}
void show_a()
{
cout << a << endl;
}
private:
int a;
};
int main()
{
exe ob;
exe *p;
ob.set_a(2);
ob.show_a();
p = &ob;
p->show_a();
(*p).show_a();
return 0;
}
2. 使用指针访问对象数组
#include <iostream>
using namespace std;
class exe
{
public:
void set_a(int x)
{
a = x;
}
void show_a()
{
cout << a << endl;
}
private:
int a;
};
int main()
{
exe ob[2];
exe *p;
ob[0].set_a(10);
ob[1].set_a(20);
p = ob;
p->show_a();
p++;
p->show_a();
return 0;
}
3. this指针
#include <iostream>
using namespace std;
class A
{
public:
A(int x1)
{
x = x1;
}
void disp()
{
cout << "x = " << x << endl;
}
private:
int x;
};
int main()
{
//C++为成员函数提供了一个名字为this的指针,这个指针叫做自引用指针。
//每当创建一个对象的时候,系统就把this指针初始化指向该对象,this指针是隐式使用的
//即this指针的值时当前调用成员函数对象的起始地址。
//例如a的disp函数实际上执行
// void disp(*this)
// {
// cout << "x = " << this->x << endl;
// }
//调用也同时处理为a.disp(&a);
A a(1), b(2);
cout << "a:";
a.disp();
cout << "b:";
b.disp();
return 0;
}
4. 显示this指针的值
#include <iostream>
using namespace std;
class A
{
public:
A(int x1)
{
x = x1;
}
void disp()
{
cout << "this = " << this << " when x = " << this->x << endl;
}
private:
int x;
};
int main()
{
A a(1), b(2), c(3);
a.disp();
b.disp();
c.disp();
return 0;
}
3.4 string类
string类运算符基本操作
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
string s1 = "ABC";
string s2 = "DEF";
string s3("GHI");
string s4, s5;
s4 = s1;
cout << "s4 is " << s4 << endl;
s5 = s1 + s2;
cout << "s1 + s2 = " << s5 << endl;
s5 = s1 + "123";
cout << "s1 + \"123\" is " << s5 << endl;
if (s3 > s1)
cout << "s3 > s1" << endl;
else
cout << "s3 < s1" << endl;
if (s4 == s1)
cout << "s4 == s1" << endl;
else
cout << "s4 <> s1" << endl;
cout << "Please input a string for s5: ";
cin >> s5;
cout << "s5 is " << s5 << endl;
return 0;
}
3.5 向函数传递对象
3.5.1 使用对象作为函数参数
#include <iostream>
using namespace std;
class Tr
{
public:
Tr(int n)
{
i = n;
}
void set_i(int n)
{
i = n;
}
int get_i()
{
return i;
}
private:
int i;
};
//重点,但是这样仍然是值传递,而不是地址传递
void sqr_it(Tr ob)
{
ob.set_i(ob.get_i() * ob.get_i());
cout << "In function ob's value of i: " << ob.get_i();
cout << endl;
}
int main()
{
Tr obj(100);
cout << "Before function, obj's vaule of i: ";
cout << obj.get_i() << endl;
sqr_it(obj);
cout << "After function, obj's value of i: ";
cout << obj.get_i() << endl;
}
3.5.2 使用对象指针作为函数参数
#include <iostream>
using namespace std;
class Tr
{
public:
Tr(int n)
{
i = n;
}
void set_i(int n)
{
i = n;
}
int get_i()
{
return i;
}
private:
int i;
};
//重点,实现了传址调用
void sqr_it(Tr *ob)
{
ob->set_i(ob->get_i() * ob->get_i());
cout << "In function ob's value of i: " << ob->get_i();
cout << endl;
}
int main()
{
Tr obj(100);
cout << "Before function, obj's vaule of i: ";
cout << obj.get_i() << endl;
sqr_it(&obj);
cout << "After function, obj's value of i: ";
cout << obj.get_i() << endl;
}
3.5.3 使用对象引用作为函数参数
#include <iostream>
using namespace std;
class Tr
{
public:
Tr(int n)
{
i = n;
}
void set_i(int n)
{
i = n;
}
int get_i()
{
return i;
}
private:
int i;
};
//重点,传址调用
void sqr_it(Tr &ob)
{
ob.set_i(ob.get_i() * ob.get_i());
cout << "In function ob's value of i: " << ob.get_i();
cout << endl;
}
int main()
{
Tr obj(100);
cout << "Before function, obj's vaule of i: ";
cout << obj.get_i() << endl;
sqr_it(obj);
cout << "After function, obj's value of i: ";
cout << obj.get_i() << endl;
}
3.6 对象的赋值与复制
3.6.1 对象赋值语句
#include <iostream>
using namespace std;
class Myclass
{
public:
void set(int i, int j)
{
a = i;
b = j;
}
void show()
{
cout << a << " " << b << endl;
}
private:
int a, b;
};
int main()
{
Myclass o1, o2;
o1.set(20, 5);
o2 = o1;//普普通通一句话
//将一个对象的值赋给另一个对象时,多数情况下都是成功的,但是当类中存在指针时,可能会产生错误
o1.show();
o2.show();
return 0;
}
3.6.2 拷贝构造函数
1. 自定义拷贝构造函数
#include <iostream>
using namespace std;
//拷贝构造函数是一中特殊的构造函数,其形参是本类对象的引用(通常只会复制对象成员,而不修改原对象,所以通常加const)
//作用是使用一个已有的对象来初始化一个新的对象
//该函数只有一个参数,并且是同类对象的引用
//每个类都必须有一个拷贝构造函数。程序员可以自定义拷贝构造函数,用于按照需要初始化对象。
//如果没有定义拷贝构造函数,那么系统自动生成一个默认的拷贝构造函数,用于复制值完全相同的对象
//一般情况下,默认构造函数能够完全胜任工作,但是如果类中有指针,可能会产生错误。
class Point
{
public:
Point(int a, int b)
{
x = a;
y = b;
}
//拷贝构造函数。传入一个本类对象的引用
Point(const Point &p)
{
x = 2 * p.x;
y = 2 * p.y;
}
void print()
{
cout << x << " " << y << endl;
}
private:
int x, y;
};
int main()
{
Point p1(30, 40);
//两种调用拷贝函数的方法
Point p2(p1);
Point p3 = p1;//如果这里前面没有Point的话,就是对象赋值语句,会赋值传入对象完全相同的值。
p1.print();
p2.print();
p3.print();
return 0;
}
2. 调用拷贝构造函数的3种情况
#include <iostream>
using namespace std;
//调用拷贝构造函数的3种情况
//1.使用一个对象去初始化另一个对象的时候
// Point p2(p1);
// Point p3 = p1;
//2.当函数的形参是类的对象,在调用函数进行形参和实参结合时,拷贝构造函数将会被调用
// 当类中有自定义的拷贝构造函数,就用自定义的拷贝构造函数,如果没有,那么调用系统自动生成的拷贝构造函数
// void fun1(Point p)
// {
// p.print();
// }
// int main()
// {
// Point p1(10, 20);
// fun(p1); //通过拷贝构造函数用p1实参初始化形参p
// return 0;
// }
//3.当函数的返回值是对象时,函数调用完毕后将返回值(对象)带回函数调用处。
// 函数结束时会调用拷贝构造函数,将p1对象复制给一个临时对象并将这个临时对象的值赋给该函数的调用处p2,之后临时对象便自动消失。
// Point fun2() //fun2函数的返回值是一个对象
// {
// Point p1(10, 30);
// return p1;
// }
// int main()
// {
// Point p2;
// P2 = fun2();
// return 0;
// }
class Point
{
public:
Point(int a0, int b = 0);//声明构造函数
Point(const Point &p);//声明拷贝构造函数,传入的参数只能是对象的引用,使用const限定,加快速度的同时防止修改
void print()
{
cout << x << " " << y << endl;
}
private:
int x, y;
};
Point::Point(int a, int b)
{
x = a;
y = b;
cout << "Using normal constuctor" << endl;
}
Point::Point(const Point &p)
{
x = 2 * p.x;
y = 2 * p.y;
cout << "Using copy constructor" << endl;
}
void fun1(Point p)//fun1的形参是类对象,会进行拷贝构造函数的调用,属于情况2
{
p.print();
}
Point fun2()//fun2的返回值是类对象,属于情况3
{
Point p4(10, 30);//定义p4,要调用普通构造函数
return p4;//返回对象,要调用拷贝构造函数
}
int main()
{
Point p1(30, 40);//第一次调用普通的构造函数
p1.print();
Point p2(p1);//第一次调用拷贝构造函数
p2.print();
Point p3 = p1;//第二次调用拷贝构造函数,如果前面没有Point的话,就是一个对象赋值语句,会赋值出完全一样的数据成员
p3.print();
fun1(p1);//第三次调用拷贝构造函数
p2 = fun2();//函数体内部第二次调用普通的构造函数,返回类对象的时候第四次调用拷贝构造函数
//程序结果不一样,搞不懂为什么错了-_-,程序结果显示返回的时候没有调用拷贝拷贝构造函数,否则的话结果p2应该是20 60
p2.print();
return 0;
}
3.7 静态成员
3.7.1 静态数据成员
1. 错误示例
#include <iostream>
#include <cstring>
using namespace std;
class Student
{
public:
Student(string name1, string stu_no1, float score1);
~Student();
void show();
void show_count_sum_ave();
private:
string name;
string stu_no;
float score;
int count;
float sum;
float ave;
};
Student::Student(string name1, string stu_no1, float score1)
{
stu_no = stu_no1;
score = score1;
++count;
sum = sum + score;
ave = sum / count;
}
Student::~Student()
{
--count;
sum = sum - score;
}
void Student::show()
{
cout << endl << "Name: " << name;
cout << endl << "No: " << stu_no;
cout << endl << "score: " << score;
}
void Student::show_count_sum_ave()
{
cout << endl << "Sum of student: " << count;
cout << endl << "Average score: " << ave;
}
int main()
{
Student stu1("LiMing", "08150201", 90);
stu1.show();
stu1.show_count_sum_ave();
cout << endl << "----------------------------" << endl;
Student stu2("ZhangDaWei", "08150202", 80);
stu2.show();
stu2.show_count_sum_ave();
return 0;
}
2. 静态数据成员的使用
#include <iostream>
#include <cstring>
using namespace std;
//如果想要达到静态数据成员一样的效果一个方法是使用全局变量,但是使用全局变量会带来不安全性,并且破坏了面向对象程序设计的信息
//隐蔽技术,与面向对象的封装性特点是矛盾的。所以应该使用静态数据成员
//在一个类中,若将一个数据成员说明为static,这种成员称为静态数据成员。与一般的数据成员不同,无论建立多少个类的对象
//都只有一个静态数据成员的拷贝,从而实现了同一个类不同对象之间的数据共享。
class Student
{
public:
Student(string name1, string stu_no1, float score1);
~Student();
void show();
void show_count_sum_ave();
private:
string name;
string stu_no;
float score;
//声明为static
static int count;
static float sum;
static float ave;
};
Student::Student(string name1, string stu_no1, float score1)
{
stu_no = stu_no1;
score = score1;
++count;
sum = sum + score;
ave = sum / count;
}
Student::~Student()
{
--count;
sum = sum - score;
}
void Student::show()
{
cout << endl << "Name: " << name;
cout << endl << "No: " << stu_no;
cout << endl << "score: " << score;
}
void Student::show_count_sum_ave()
{
cout << endl << "Sum of student: " << count;
cout << endl << "Average score: " << ave;
}
//静态数据成员的初始化,静态成员的初始化应该在类外单独进行,而且应该在定义对象之前进行。一般在主函数main之前
//静态数据成员属于类对象的集合,所以访问需要用“类名::静态数据成员名”格式访问(注意还要加上数据类型)。
int Student::count = 0;
float Student::sum = 0.0;
float Student::ave = 0.0;
int main()
{
Student stu1("LiMing", "08150201", 90);
stu1.show();
stu1.show_count_sum_ave();
cout << endl << "----------------------------" << endl;
Student stu2("ZhangDaWei", "08150202", 80);
stu2.show();
stu2.show_count_sum_ave();
return 0;
}
3. 公有静态数据成员的访问
#include <iostream>
using namespace std;
//静态数据成员与静态变量一样,是在编译时创建并初始化。它在该类的任何对象被建立之前就存在。
//因此,公有的静态数据成员可以在对象定义之前就被访问。对象定义后,公有的静态数据成员,也可以通过对象进行访问。
//但是私有的静态数据成员不能在类外直接访问,必须通过公有的成员函数访问。
//静态数据成员主要用作类的所有对象所公有的数据,如统计总数、平均数。
//C++支持静态数据成员的一个重要原因是避免使用全局变量,全局变量会破坏面向对象程序设计的封装特性。
class myclass
{
public:
static int i;//关注
int geti()
{
return i;
}
};
int myclass::i = 0;//关注
int main()
{
myclass::i = 200;//关注,推荐的访问形式,使用“类名::静态数据成员名”访问
myclass ob1, ob2;
cout << "ob1.i = " << ob1.geti() << endl;
cout << "ob2.i = " << ob2.geti() << endl;
ob1.i = 300;//关注
cout << "ob1.i = " << ob1.geti() << endl;
cout << "ob2.i = " << ob2.geti() << endl;
return 0;
}
3.7.2 静态成员函数
1. 静态成员函数访问静态数据成员
#include <iostream>
using namespace std;
//类定义中,前面有static说明的成员函数称为静态成员函数。
//静态成员函数属于整个类,是该类所有对象共享的成员函数,而不属于类中的某个对象。
//调用公有静态成员函数一般有以下格式:
//1.类名::静态成员函数名(实参表)
//2.对象.静态成员函数名(实参表)
//3.对象指针->静态成员函数名(实参表)
//1.一般情况下,静态成员函数主要用来访问静态数据成员。当它与静态数据成员一起使用时,达到了对同一个类中对象之间共享数据的目的。
//2.私有静态成员函数不能做类外部的函数和对象访问。
//3.使用静态成员函数的一个原因时,可以在建立任何对象之前调用静态成员函数,来处理静态数据成员,这是普通成员函数不能实现的功能。
//4.编译系统将静态成员函数限定为内部连接,也就是说,与现行文件相连接的其他文件中的同名函数不会与该函数发生冲突,
// 维护了函数使用的安全性,这是使用静态成员函数的另一个原因。
//5.静态成员函数和非静态成员函数的重要区别是:非静态成员函数有this指针,而静态成员函数中没有this指针。
// 静态成员函数可以直接访问本类中的静态数据成员,因为静态数据成员同样是属于类的,可以直接访问。
// 一般来说,静态成员函数不访问类中的非静态成员,若确实要访问非静态成员
// 静态成员函数只能通过对象名(或对象指针,对象引用)访问该对象的非静态成员
class Small_cat
{
public:
Small_cat(double w);//构造函数
void display();//非静态成员函数
static void total_disp();//静态成员函数
private:
double weight;//普通数据成员
static double total_weight;//静态数据成员
static double total_number;//静态数据成员
};
Small_cat::Small_cat(double w)//定义构造函数
{
weight = w;
total_weight += w;
total_number++;
}
void Small_cat::display()//定义非静态成员函数
{
cout << "The weight of this cat is " << weight << "kilograms." << endl;
}
void Small_cat::total_disp()//定义静态成员函数,显示小猫的质数和总质量
{
cout << total_number << " cats' total weight is " << total_weight << "kilograms." << endl;
}
double Small_cat::total_weight = 0;//静态数据成员的初始化
double Small_cat::total_number = 0;//静态数据成员的初始化
int main()
{
Small_cat::total_disp();//可以在建立任何对象之前调用静态成员函数,重点关注这里
Small_cat w1(0.5), w2(0.6), w3(0.4);
w1.display();
w2.display();
w3.display();
Small_cat::total_disp();
//调用公有静态成员函数一般有以下格式:
//1.类名::静态成员函数名(实参表) 最推荐的写法,因为静态成员函数是类的一部分,而不是对象的一部分。
//2.对象.静态成员函数名(实参表)
//3.对象指针->静态成员函数名(实参表)
return 0;
}
2. 静态成员函数访问非静态数据成员
#include <iostream>
using namespace std;
// 一般来说,静态成员函数不访问类中的非静态成员,若确实要访问非静态成员
// 静态成员函数只能通过对象名(或对象指针,对象引用)访问该对象的非静态成员
class Small_cat
{
public:
Small_cat(double w);//构造函数
static void display(Small_cat &w);//静态成员函数,主要知识点
static void total_disp();//静态成员函数
private:
double weight;//普通数据成员
static double total_weight;//静态数据成员
static double total_number;//静态数据成员
};
Small_cat::Small_cat(double w)//定义构造函数
{
weight = w;
total_weight += w;
total_number++;
}
void Small_cat::display(Small_cat &w)//定义静态成员函数,主要知识点,注意辨析访问静态数据成员不同的情况
{
cout << "The weight of this cat is " << w.weight << "kilograms." << endl;
}
void Small_cat::total_disp()//定义静态成员函数,显示小猫的质数和总质量
{
cout << total_number << " cats' total weight is " << total_weight << "kilograms." << endl;
}
double Small_cat::total_weight = 0;//静态数据成员的初始化
double Small_cat::total_number = 0;//静态数据成员的初始化
int main()
{
Small_cat::total_disp();//可以在建立任何对象之前调用静态成员函数
Small_cat w1(0.5), w2(0.6), w3(0.4);
Small_cat::display(w1);//主要知识点,如何通过静态数据成员访问非静态数据成员,注意观察
Small_cat::display(w2);
Small_cat::display(w3);
Small_cat::total_disp();
return 0;
}
3.8 友元
3.8.1 友元函数
1. 非成员函数声明为友元函数
#include <iostream>
using namespace std;
//有时候需要在类的外部访问类的私有成员(或保护成员)。因此,就需要寻找一种途径,在不放弃私有成员(或保护成员)数据安全性的情况下
//使得一个普通函数或者类的成员函数可以访问到封装于某一类的信息(私有、保护成员),在C++中使用友元作为实现这个要求的辅助手段。
//C++中友元为数据隐藏这堵不透明的墙开了一个小孔,外界可以通过这个小孔窥视类内部的秘密,友元是一扇通向私有(保护)成员的后门。
//既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数,统称为友元函数。友元函数不是当前类的成员函数,而是独立于当前类的外部函数
//但它可以访问该类所有的成员,包括私有成员、保护成员和公有成员。类中声明友元函数时,函数名前加上关键字friend。
//此声明可以放在类内部,也可以定义在类的外部。
class Girl
{
public:
Girl(string n, int d)
{
name = n;
age = d;
}
friend void disp(Girl &);
private:
string name;
int age;
};
//因为友元函数不是类的成员,所以必须传入一个对象或对象指针或对象引用来访问该对象的数据成员,可以看出,友元函数能够访问私有数据
//但是友元函数不是一个成员函数,所以可以看到不需要前面加上“类名::”。
void disp(Girl &x)
{
cout << "The name of this girl is: " << x.name << ", age is: " << x.age << endl;
}
int main()
{
Girl g1("ChenXiaoli", 18);
disp(g1);
return 0;
}
//为什么引入友元函数机制,友元函数是对类的封装机制的补充,这种机制,一个类可以赋予某些函数访问它的私有成员的特权。
//声明了一个类的友元函数,就可以利用这个函数直接访该类的私有数据,从而提高程序运行的效率。如果没有友元机制,外部函数
//访问类的私有数据,必须通过调用公有的成员函数,这在需要频繁调用私有数据的情况下,会带来比较大的开销,从而降低成员的运行效率。
//但是,引入友元机制并不是使数据称为公有的或全局的,未经授权的其他函数仍然不能直接访问这些私有数据。因此,慎重、合理地使用
//友元机制不会彻底丧失安全性,不会使软件可维护性大幅度降低。
//其次,友元提供了不同类的成员函数之间、类的成员函数于一般函数之间进行数据共享的机制。尤其当一个函数需要访问多个类时
//友元函数非常有用,普通的成员函数只能访问其所属的类,但是多个类的友元函数能够访问相关的所有类的数据。
2. 成员函数声明为友元函数
#include <iostream>
using namespace std;
//有时候需要在类的外部访问类的私有成员(或保护成员)。因此,就需要寻找一种途径,在不放弃私有成员(或保护成员)数据安全性的情况下
//使得一个普通函数或者类的成员函数可以访问到封装于某一类的信息(私有、保护成员),在C++中使用友元作为实现这个要求的辅助手段。
//C++中友元为数据隐藏这堵不透明的墙开了一个小孔,外界可以通过这个小孔窥视类内部的秘密,友元是一扇通向私有(保护)成员的后门。
//既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数,统称为友元函数。友元函数不是当前类的成员函数,而是独立于当前类的外部函数
//但它可以访问该类所有的成员,包括私有成员、保护成员和公有成员。类中声明友元函数时,函数名前加上关键字friend。
//此声明可以放在类内部,也可以定义在类的外部。
class Girl
{
public:
Girl(string n, int d)
{
name = n;
age = d;
}
friend void disp(Girl &);
private:
string name;
int age;
};
//因为友元函数不是类的成员,所以必须传入一个对象或对象指针或对象引用来访问该对象的数据成员,可以看出,友元函数能够访问私有数据
//但是友元函数不是一个成员函数,所以可以看到不需要前面加上“类名::”。
void disp(Girl &x)
{
cout << "The name of this girl is: " << x.name << ", age is: " << x.age << endl;
}
int main()
{
Girl g1("ChenXiaoli", 18);
disp(g1);
return 0;
}
//为什么引入友元函数机制,友元函数是对类的封装机制的补充,这种机制,一个类可以赋予某些函数访问它的私有成员的特权。
//声明了一个类的友元函数,就可以利用这个函数直接访该类的私有数据,从而提高程序运行的效率。如果没有友元机制,外部函数
//访问类的私有数据,必须通过调用公有的成员函数,这在需要频繁调用私有数据的情况下,会带来比较大的开销,从而降低成员的运行效率。
//但是,引入友元机制并不是使数据称为公有的或全局的,未经授权的其他函数仍然不能直接访问这些私有数据。因此,慎重、合理地使用
//友元机制不会彻底丧失安全性,不会使软件可维护性大幅度降低。
//其次,友元提供了不同类的成员函数之间、类的成员函数于一般函数之间进行数据共享的机制。尤其当一个函数需要访问多个类时
//友元函数非常有用,普通的成员函数只能访问其所属的类,但是多个类的友元函数能够访问相关的所有类的数据。
3. 一个函数同时定义为两个类的友元函数
#include <iostream>
#include <cstring>
using namespace std;
class Boy;//对Boy类的提前引用声明
class Girl
{
public:
Girl(string N, int A);
friend void prdata(const Girl &, const Boy &);//声明函数prdata为类Girl的友元函数
private:
string name;
int age;
};
Girl::Girl(string N, int A)
{
name = N;
age = A;
}
class Boy
{
public:
Boy(string N, int A);
friend void prdata(const Girl &plg, const Boy &plb);//声明函数prdata为类Boy的友元函数
private:
string name;
int age;
};
Boy::Boy(string N, int A)
{
name = N;
age = A;
}
void prdata(const Girl &plg, const Boy &plb)
{
cout << "Girl's name: " << plg.name << endl;
cout << "Girl's age: " << plg.age << endl;
cout << "Boy's name: " << plb.name << endl;
cout << "Boy's age: " << plb.age << endl;
}
int main()
{
Girl g1("ZhangXiaohao", 12);
Girl g2("LiFang", 13);
Girl g3("WangHong", 12);
Boy b1("ChenDalin", 11);
Boy b2("ZhaoChao", 13);
Boy b3("BaiXiaoguang", 12);
prdata(g1, b1);
prdata(g2, b2);
prdata(g3, b3);
return 0;
}
3.8.2 友元类
#include <iostream>
#include <string>
using namespace std;
class Girl;
class Boy
{
public:
Boy(string n, int d)
{
name = n;
age = d;
}
void disp1(Girl &);//disp1和disp2均为类Boy的成员函数
void disp2(Girl &);
private:
string name;
int age;
};
class Girl
{
public:
Girl(string n, int d)
{
name = n;
age = d;
}
friend Boy;//声明类Boy为类Girl的友元类,则类Boy中的所有成员函数都是Gril类的友元成员函数,
//Boy的成员函数能够访问Girl类中的所有成员(包括私有成员)
//这条语句可以放在公有部分,也可以放在私有部分
private:
string name;
int age;
};
void Boy::disp1(Girl &x)
{
cout << "Boy's name: " << name << endl;
cout << "Girl's name: " << x.name << endl;
}
void Boy::disp2(Girl &x)
{
cout << "Boy's age: " << age << endl;
cout << "Girl's age: " << x.age << endl;
}
int main()
{
Boy b1("ChenDalin", 11);
Girl g1("ZhangDahao", 12);
b1.disp1(g1);
b1.disp2(g1);
return 0;
}
//说明:友元关系是单向的,不具有交换性。同时,友元关系也不具有传递性。
3.9 类的组合
3.9.1 对象成员的初始化
#include <iostream>
using namespace std;
//在一个类中内嵌另一个类的对象作为数据成员,称为类的组合。该内嵌对象称为对象成员,也称为子对象。
//对于类的组合,一大问题是内嵌对象的初始化
class A
{
public:
A(int x1, float y1)
{
x = x1;
y = y1;
}
void show()
{
cout << endl << "x = " << x << " y = " << y;
}
private:
int x;
float y;
};
class B
{
public:
B(int x1, float y1, int z1) : a(x1, y1)//类B的构造函数,含有初始化列表,用于对内嵌对象a进行初始化
//先调用a的构造函数初始化a,然后再执行构造函数体,析构函数的调用顺序则相反。
{
z = z1;
}
void show
{
a.show();
cout << "z = " << z;
}
private:
A a;//重点,类A的对象a为类B的对象成员
int z;
}
3.9.2 对象成员的应用
#include <iostream>
using namespace std;
//在一个类中内嵌另一个类的对象作为数据成员,称为类的组合。该内嵌对象称为对象成员,也称为子对象。
//对于类的组合,一大问题是内嵌对象的初始化
class A
{
public:
A(int x1, float y1)
{
x = x1;
y = y1;
}
void show()
{
cout << endl << "x = " << x << " y = " << y;
}
private:
int x;
float y;
};
class B
{
public:
B(int x1, float y1, int z1) : a(x1, y1)//类B的构造函数,含有初始化列表,用于对内嵌对象a进行初始化
//先调用a的构造函数初始化a,然后再执行构造函数体,析构函数的调用顺序则相反。
{
z = z1;
}
void show
{
a.show();
cout << "z = " << z;
}
private:
A a;//重点,类A的对象a为类B的对象成员
int z;
}
3.10 常类型
3.10.1 常引用
#include <iostream>
using namespace std;
//使用常引用作为参数,防止对实参进行不必要的修改,实际应用中,常引用往往用来作为函数的形参,这样的参数称为常参数
int add(const int &i, const int &j);
int main()
{
int a = 20;
int b = 30;
cout << a << " + " << b << " = " << add(a, b) << endl;
a = 15;
b = 50;
cout << a << " + " << b << " = " << add(a, b) << endl;
return 0;
}
int add(const int &i, const int &j)
{
//i += 20;如果多了这个语句,会出现编译错误
return i + j;
}
3.10.2 常对象
常对象和非常对象进行对比
#include <iostream>
using namespace std;
class Sample
{
public:
int m;
Sample(int i, int j)
{
m = i;
n = j;
}
void setvalue(int i)
{
n = i;
}
void display()
{
cout << "m = " << m << endl;
cout << "n = " << n << endl;
}
private:
int n;
};
int main()
{
const Sample a(10, 20);//下面的语句会报错
//const Sample a(10, 20)或Sample const a(10, 20)均可以
a.setvalue(40);//不能改变数据类型的值
a.m = 30;//不能改变数据类型的值
a.display();//C++不允许常对象调用普通的成员函数
return 0;
}
3.10.3 常对象成员
1. 常数据成员
#include <iostream>
using namespace std;
class Date
{
public:
Date(int y, int m, int d);
void showDate();
private:
const int year;//常数据成员
const int month;
const int day;
};
Date::Date(int y, int m, int d) : year(y), month(m), day(d)
//year, month, day都是常数据成员,C++规定只能通过构造函数的成员初始化列表才能对常数据成员进行初始化
//一旦初始化成功后,不能改变数据成员的值
{}//别忘了函数的括号
void Date::showDate()
{
cout << year << '.' << month << '.' << day << endl;
}
int main()
{
Date date1(1998, 4, 28);
date1.showDate();
return 0;
}
2. 常成员函数
#include <iostream>
using namespace std;
//常成员函数可以访问常数据成员,也可以访问普通数据成员。常数据成员可以被常成员函数访问,也可以被普通成员函数访问。
//如果将一个对象声明为常对象,则通过该对象只能调用它的常成员函数,而不能调用普通的成员函数。
//常成员函数是常对象唯一的对外接口。
//常成员函数不能更新对象的数据成员,也不能调用该类中普通成员函数,这保证了常成员函数中绝对不会更新数据成员的值。
//访问特性表
//数据成员 普通成员函数 常成员函数
//普通数据成员 可以访问,也可以改变其值 可以访问,但不能改变其值
//常数据成员 可以访问,但不可以改变其值 可以访问,但不能改变其值
//常对象的数据成员 不允许访问和更改 可以访问,但不能改边其值
class Date
{
public:
Date(int y, int m, int d);
void showDate();//声明普通的成员函数showDate
void showDate() const;//声明常成员函数showDate,它和普通的成员函数互为重载函数,注意格式,这个是在最后面加上const
private:
int year;
int month;
int day;
};
Date::Date(int y, int m, int d) : year(y), month(m), day(d)
{}
void Date::showDate()
{
cout << "showDate1: " << endl;
cout << year << '.' << month << '.' << day << endl;
}
void Date::showDate() const//函数原型和函数定义都在参数列表后面带有const
{
cout << "showDate2: " << endl;
cout << year << '.' << month << '.' << day << endl;
}
int main()
{
Date date1(1998, 4, 28);
date1.showDate();
const Date date2(2002, 11, 14);
date2.showDate();//常成员函数调用的时候可以不用加const
return 0;
}
第4章 派生类与继承
4.1 派生类的概念
4.1.1 初识派生类与继承
class Person
{
public:
void print()
{
cout << "name: " << name << endl;
cout << "age: " << age << endl;
cout << "sex: " << sex << endl;
}
protected://protected关键字将相关数据成员声明为保护成员,可以被本类的成员函数访问
//也可以被本类的子类成员函数访问,但是再类外的任何访问都是非法的,即它是半隐蔽的
string name;
int age;
char sex;
};
//声明子类的一般格式: class 子类名 : 继承方式 父类名 {子类新增的数据成员和成员函数};
class Employee : public Person//声明子类Employee公有继承了父类Person,如果不写出继承方式,默认为私有继承
{
public:
void print1
{
print();
cout << "department: " << department << endl;
cout << "salary: " << salary << endl;
}
private:
string department;
float salary;
};
//派生类的构成
// 构造一个派生类一般包括以下3部分工作
// 1.派生类从基类接收成员
// 在C++的类继承中,派生类把基类的全部成员(除了构造函数和析构函数之外)接收过来。
// 2.调整从基类中接收的成员
// 派生类不能对接收基类的成员进行选择,但是可以对这些成员进行某些调整。对于基类成员的调整包括两方面,
// 一方面是改变基类成员在派生类中的访问属性,这主要是通过派生类声明时的继承方式来控制的,例如通过继承可以使基类中
// 的公有成员在派生类中的访问属性变为私有,另一方面是派生类可以对基类的成员进行重定义,即在派生类中声明一个基类
// 成员同名的成员,则派生类中的新成员会覆盖基类的同名成员,这时在派生类中或者通过派生类对象,直接使用成员名就能访问
// 到派生类中声明的同名成员。但应该注意,如果是成员函数需要覆盖,则不仅应该使得函数名相同,而且函数的参数表也应该
// 相同,如果不相同,则称派生类重载了基类的成员函数,而不是覆盖了基类的同名函数
// 3.在派生类中增加新成员
// 在派生类中添加新的成员体现了派生类对基类功能的拓展,是继承和派生机制的核心。
// 注意,在继承过程中,基类的构造函数和析构函数是不能被继承的,因此在声明派生类时,一般需要在派生类中定义新的
// 构造函数和析构函数。
4.1.2 基类成员在派生类中的访问属性
从基类中继承过来的成员在派生类中的访问属性是由继承方式控制的。
类的继承方式由public、protected和private这3种,不同的继承方式导致不同访问属性的基类成员在派生类中访问属性也不同。
在派生类中,从基类继承过来的成员可以按访问属性划分为4中:不可直接访问、公有(public)、保护(protected)和私有(private)。
1.基类中的私有成员
无论哪种继承方式,基类中的私有成员不允许派生类继承,即在派生类中无法直接访问。
2.基类中的公有成员
当类的继承方式为公有继承时,基类中的所有公有成员在派生类中仍以公有成员的身份出现。
当类的继承方式为私有继承时,基类中的所有公有成员在派生类中都以私有成员的身份出现。
当类的继承方式为保护继承时,基类中的所有公有成员在派生类中都以保护成员的身份出现。
3.基类中的保护成员
当类的继承方式为公有继承时,基类中的所有公有成员在派生类中仍以保护成员的身份出现。
当类的继承方式为私有继承时,基类中的所有公有成员在派生类中都以私有成员的身份出现。
当类的继承方式为保护继承时,基类中的所有公有成员在派生类中仍以保护成员的身份出现。
综上,private > protected > public。
4.1.3 派生类对基类成员的访问规则
1. 公有继承
#include <iostream>
using namespace std;
// 公有继承的访问规则
//基类中的成员 私有成员 公有成员 保护成员
//内部访问 不可访问 可访问 可访问
//对象访问 不可访问 可访问 不可访问
//需要重复强调,派生类以公有继承的方式继承了基类,并不意味着派生类可以访问基类的私有成员
class Base
{
public:
void setxy(int m, int n)
{
x = m;
y = n;
}
void showxy()
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
}
private:
int x;
protected:
int y;
};
class Derived : public Base
{
public:
void setxyz(int m, int n, int l)
{
setxy(m, n);//setxy在派生类中是public成员,派生类成员函数可以访问
z = l;
}
void showxyz()
{
cout << "x = " << x << endl;//错误,x在类Derived中为不可直接访问成员(重点关注这两行)
cout << "y = " << y << endl;//正确,y在类Derived中为保护成员,派生类成员函数可以访问
cout << "z = " << z << endl;
}
private:
int z;
};
int main()
{
Derived obj;
obj.setxyz(30, 40, 50);
obj.showxyz();
obj.y = 60;//错误,y在类Derived中为保护成员,派生类对象不能访问它
obj.showxyz();
return 0;
}
2. 保护继承
#include <iostream>
using namespace std;
// 保护继承的访问规则
//基类中的成员 私有成员 公有成员 保护成员
//内部访问 不可访问 可以访问 可以访问
//对象访问 不可访问 不可访问 不可访问
class Base
{
public:
int z;
void setx(int i)
{
x = i;
}
int getx()
{
return x;
}
private:
int x;
protected:
int y;
};
class Derived : protected Base
{
public:
int p;
void setall(int a, int b, int c, int d, int e, int f);
void show();
private:
int m;
protected:
int n;
};
void Derived::setall(int a, int b, int c, int d, int e, int f)
{
x = a;//错误,派生类中Derived中,x为不可直接访问成员,可以修改为setx(a);
y = b;//正确,y在派生类Derived中为保护成员,派生类对象能访问它
z = c;//正确,z在派生类Derived中为保护乘员,派生类对象能访问它
m = d;
n = e;
p = f;
}
void Derived::show()
{
cout << "x = " << x << endl;//只有这里错误
cout << "y = " << y << endl;
cout << "z = " << z << endl;
cout << "m = " << m << endl;
cout << "n = " << n << endl;
}
int main()
{
Derived obj;
obj.setall(1, 2, 3, 4, 5, 6);
obj.show();
cout << "y = " << y << endl;//错误,y在类Derived中为保护成员,派生类对象不能访问它
cout << "p = " << p << endl;//正确,p在类Derived中为公有成员,派生类对象能访问它
return 0;
}
3. 私有继承
私有继承1
#include <iostream>
using namespace std;
//私有继承的访问规则
// 基类中的成员 私有成员 公有成员 保护成员
// 内部访问 不可访问 可访问 可访问
// 对象访问 不可访问 不可访问 不可访问
class Base
{
public:
void setx(int n)
{
x = n;
}
void show()
{
cout << x <<endl;
}
private:
int x;
};
class Derived : private Base
{
public:
void setxy(int n, int m)//基类的setx函数可以在派生类中为私有成员,派生类成员函数可以访问
{
setx(n);//正确,成员函数setxy可以访问本类的私有成员y
y = m;
}
void showxy()
{
cout << x;//错误,派生类成员函数不能直接访问基类的私有成员x,这里改成showx()就对了
cout << y << endl;//正确,成员函数showxy可以访问本类的私有成员y
}
private:
int y;
};
int main()
{
Derived obj;
obj.setx(10);//错误,setx在派生类中为私有成员,派生类对象不能访问
obj.showx();//错误,showx在派生类中为私有成员,派生类对象不能访问
obj.setxy(20, 30);//正确,setxy在类Derived为公有成员,派生类对象能访问
obj.showxy();//正确,showxy在类Derived为公有成员,派生类对象能访问
return 0;
}
私有继承2
#include <iostream>
using namespace std;
class Base
{
public:
void seta (int sa)
{
a = sa;
}
void showa()
{
cout << "a = " << a << endl;
}
protected:
int a;
};
class Derive1 : private Base
{
public:
void setab(int sa, int sb)
{
a = sa;//a在派生类中为私有成员,派生类成员函数能够访问
b = sb;
}
void showab()
{
cout << "a = " << a << endl;//a在派生类中为私有成员,派生类成员函数能够访问
cout << "b = " << b << endl;
}
protected:
int b;
};
class Derive2 : private Derive1
{
public:
void setabc(int sa, int sb, int sc)
{
setab(sa, sb);
c = sc;
}
void showabc()
{
cout << "a = " << a << endl;//错误,a在类Derive1中为私有成员,经过私有继承后不能再直接访问
cout << "b = " << b << endl;//正确,b在类Derive2中为私有成员
cout << "c = " << c << endl;
}
private:
int c;
};
int main()
{
Base op1;
op1.seta(1);
op1.showa();
Derive1 op2;
op2.setab(2, 3);
op2.showab();
Derive2 op3;
op3.setabc(4, 5, 6);
op3.showabc();
return 0;
}
//经过私有继承之后,所以基类的成员都成为了派生类的私有成员(原来是public或者protected)
//或变成了不可直接访问的成员(原来是private),如果进一步派生的话,基类的全程成员都无法在新的派生类中被访问
//因此,私有继承之后,基类的成员无法在以后的派生类中发挥作用,实际上时相当于终止了基类功能的继承派生
//基于这种原因,私有继承的实际应用很少
4.2派生类的构造函数和析构函数
4.2.1 派生类构造函数和析构函数的执行顺序
#include <iostream>
using namespace std;
//创建时,先调用父类的构造函数,再调用子类的构造函数
//撤销时,先调用子类的构造函数,再调用父类的构造函数
class Base
{
public:
Base()
{
cout << "Constructing base class" << endl;
}
~Base()
{
cout << "Destructing base class" << endl;
}
};
class Derived : public Base
{
public:
Derived()
{
cout << "Constructing derived class" << endl;
}
~Derived()
{
cout << "Destructring derived class" << endl;
}
};
int main()
{
Derived obj;
return 0;
}
4.2.2 构造规则
1. 简单派生类的构造函数
当基类含有带参数的构造函数,派生类构造函数的构造方法
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student(int number1, string name1, float score1)
{
number = number1;
name = name1;
score = score1;
}
void print()
{
cout << "number: " << number << endl;
cout << "name: " << name << endl;
cout << "score: " << score << endl;
}
protected:
int number;
string name;
float score;
};
class UStudent : public Student
{
public:
UStudent(int number1, string name1, float score1, string major1) : Student(number1, name1, score1)
//关注重点,这里调用了基类的构造函数
//派生类不能继承基类的构造函数和析构函数,当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数给参数给基类构造函数的途径
//如果基类构造函数没有参数,则可以省略调用基类的构造函数
{
major = major1;
}
void print1()
{
print();
cout << "major: " << major << endl;
}
private:
string major;
};
int main()
{
UStudent stu(22116, "ZhangZhi", 95, "Computer science");
stu.print1();
return 0;
}
//如果构造函数定义在外部的写法
//类里面的函数声明
//UStudent(int number1, string name1, float score1, string major1); //这里就不用调用基类的构造函数,只需要列出形参表
//外部的函数定义
//UStudent::UStudent(int number1, string name1, float score1, string major1) : Student(number1, name1, score1)
// {
// major = major1;
// }
//当基类的构造函数不带参数时,派生类不一定需要定义构造函数,然而当基类的构造函数哪怕只带有一个参数,所有的派生类都必须定义构造函数
//甚至所定义的派生类构造函数函数体为空,派生类构造函数仅仅将所接收的参数传递给基类的构造函数
//class Base
// {
// public:
// Base(int n)
// {
// i = n;
// }
// private:
// int i;
// };
// class Derived : public Base
// {
// public:
// Derived(int n) : Base(n)
// {}
// }
2. 派生类的析构函数
简单派生类的构造函数和析构函数的执行顺序
#include <iostream>
using namespace std;
//基类的清理工作仍然由基类的析构函数负责,由于析构函数是不带参数的,在派生类中是否要自定义析构函数与它所属
//基类的析构函数无关。在执行派生类的析构函数时,系统会自动调用基类的析构函数,对基类的对象进行清理。
//析构函数的调用顺序和构造函数正好相反:先执行派生类的析构函数,再执行基类的析构函数。
class A
{
public:
A()
{
cout << "Constructing A class" << endl;
}
~A()
{
cout << "Destructing A class" << endl;
}
};
class B : public A
{
public:
B()
{
cout << "Constructing B class" << endl;
}
~B()
{
cout << "Destructing B class" << endl;
}
};
int main()
{
B b;
return 0;
}
3. 含有对象成员(子对象)的派生类的构造函数
含有对象成员的派生类构造函数和析构函数的执行顺序
#include <iostream>
using namespace std;
class Base
{
public:
Base(int i)
{
x = i;
cout << "Constructing base class" << endl;
}
~Base()
{
cout << "Destructing base class" << endl;
}
void show()
{
cout << "x = " << x << endl;
}
private:
int x;
};
class Derived : public Base
{
public:
//派生类中含有内嵌的对象成员(也称子对象)时的构造函数
//1.构造函数的执行顺序:调用基类的构造函数,对基类数据成员初始化
//2.调用内嵌对象成员的构造函数,对内嵌对象成员的数据成员初始化
//(派生类含有多个内嵌对象成员时,调用内嵌对象成员的构造函数顺序由它们在类中声明的顺序确定)
//3.执行派生类的构造函数体,对派生类数据成员初始化
//撤销对象的时候,析构函数的调用顺序和构造函数的调用顺序正好相反
Derived(int i) : Base(i), d(i)
{
cout << "Constructing derived class" << endl;
}
~Derived()
{
cout << "Destructing derived class" << endl;
}
private:
Base d;
};
int main()
{
Derived obj(5);
obj.show();
return 0;
}
含有多个对象成员的派生类构造函数的执行顺序
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student(int number1, string name1, float score1)
{
number = number1;
name = name1;
score = score1;
}
void print()
{
cout << "Stu_ID: " << number << endl;
cout << "Name: " << name << endl;
cout << "Score: " << score << endl;
}
protected:
int number;
string name;
float score;
};
class UStudent : public Student
{
public:
//多个内嵌对象成员的初始化通过调用派生类的构造函数来实现
UStudent(int number1, string name1, float score1, int number2, string name2, float score2,
int number3, string name3, float score3, string major1) : Student(number1, name1, score1),
auditor2(number3, name3, score3), auditor1(number2, name2, score2)
//内嵌对象成员的构造函数顺序和派生类构造函数中的声明没有关系,看类中的声明顺序
//如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化
{
major = major1;
}
void print()
{
cout << "Student is " << endl;
Student::print();
cout << "major is " << major << endl;
}
void print_auditor1()
{
cout << "auditor is " << endl;
auditor1.print();
}
void print_auditor2()
{
cout << "auditor is " << endl;
auditor2.print();
}
private:
string major;
Student auditor1;
Student auditor2;
};
int main()
{
UStudent stu(2001, "ZhangZhi", 95, 3001, "WangDaBing", 66, 3002, "ZhangQianQian",
50, "Information security");
stu.print();
stu.print_auditor1();
stu.print_auditor2();
return 0;
}
4.3 调整基类成员在派生类中的访问属性的其他方法
4.3.1 同名成员
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student(int number1, string name1, float score1)
{
number = number1;
name = name1;
score = score1;
}
void print()
{
cout << "number: " << number << endl;
cout << "name: " << name << endl;
cout << "score: " << score << endl;
}
protected:
int number;
string name;
float score;
};
class UStudent : private Student
{
public:
UStudent(int number1, string name1, float score1, string major1) : Student(number1, name1, score1)
{
major = major1;
}
void print()//在派生类中重新定义了成员函数print
{
Student::print();//使用基类Student的成员函数print,方法是加上基类名和作用域运算符
cout << "major: " << major << endl;
}
private:
string major;
};
int main()
{
UStudent stu(22116, "ZhangZhi", 95, "Information Security");
stu.print();
return 0;
}
4.3.2 访问声明
1. 为什么引入访问生命
#include <iostream>
using namespace std;
//对于类B中的print函数能够通过访问声明的形式进行访问A中的成员
//但是我们希望基类的个别成员能够不限制于继承方式,改变在派生类中的访问方式,为此,C++中提供了访问生命的特殊机制
//能够调整基类的某些成员,使之在派生类中保持原来的访问属性。
class A
{
public:
A(int x1)
{
x = x1;
}
void print()
{
cout << "x = " << x;
}
private:
int x;
};
class B : private A
{
public:
B(int x1, int y1) : A(x1)
{
y = y1;
}
void print()
{
A::print();
}
private:
int y;
};
int main()
{
B b(10, 20);
b.print();
return 0;
}
2. 访问声明
#include <iostream>
using namespace std;
class A
{
public:
A(int x1)
{
x = x1;
}
void print()
{
cout << "x = " << x;
}
private:
int x;
};
class B : private A
{
public:
B(int x1, int y1) : A(x1)
{
y = y1;
}
//访问声明,把基类的公有成员函数print调整为派生类的公有成员函数(只能调整为基类同样的访问属性)
A::print; //注意,访问声明中不能带有返回类型和参数表。同样,数据成员也能够使用访问声明
//void A::print; 和 A::print(); 和 void A::print();都是错误的写法
private:
int y;
};
int main()
{
B b(10, 20);
b.print();
return 0;
}
4.4 多重继承
4.4.1 多重继承派生类的声明
多重继承情况下派生类的访问属性
#include <iostream>
using namespace std;
class X
{
public:
void setX(int x)
{
a = x;
}
void showX()
{
cout << "a = " << a << endl;
}
private:
int a;
};
class Y
{
public:
void setY(int x)
{
b = x;
}
void showY()
{
cout << "b = " << b << endl;
}
private:
int b;
};
//对基类成员的访问必须是无二义性的,如果继承了两个基类,基类中有同名函数
//则派生类中访问同名函数需要使用成员名限定来消除二义性
//obj.X::f();
//obj.Y::f();
class Z : public X, private Y
{
public:
void setZ(int x, int y)
{
c = x;
setY(y);
}
void showZ()
{
showY();
cout << "c = " << c << endl;
}
private:
int c;
};
int main()
{
Z obj;
obj.setX(3);//正确,成员函数setX在Z中仍是公有成员
obj.showX();//正确,成员函数showX在Z中仍是公有成员
obj.setY(4);//错误,成员函数setY在Z中已经成为私有成员
obj.showY();//错误,成员函数showY在Z中已成为私有成员
obj.setZ(6, 8);
obj.showZ();
return 0;
}
4.4.2 多重继承下派生类的构造函数和析构函数
#include <iostream>
using namespace std;
class X
{
public:
X(int sa)
{
a = sa;
}
int getX()
{
return a;
}
~X()
{
cout << "X_Destructor called." << endl;
}
private:
int a;
};
class Y
{
public:
Y(int sb)
{
b = sb;
}
int getY()
{
return b;
}
~Y()
{
cout << "Y_Destructor called." << endl;
}
private:
int b;
};
class Z : public X, private Y
{
public:
Z(int sa, int sb, int sc) : X(sa), Y(sb)
{
c = sc;
}
int getZ()
{
return c;
}
int getY()
{
return Y::getY();
}
~Z()
{
cout << "Z_Destructor called." << endl;
}
private:
int c;
};
int main()
{
//多重继承的构造函数的执行顺序与单继承构造函数的执行顺序相同,也是先执行基类的构造函数
//然后执行对象成员的构造函数,最后执行派生类构造函数的原则。
//在多个基类之间,则严格按照派生类声明时从左到右的顺序来排列先后。
//由于析构函数时不带参数的,在派生类中是否要定义析构函数与它所属的基类无关,所以和单继承情况类似
//基类的析构函数不会因为派生类没有析构函数而得不到执行,它们各自是独立的。
//析构函数和构造函数的执行顺序恰好相反。
Z obj(2, 4, 6);
int ma = obj.getX();
cout << "a = " << ma << endl;
int mb = obj.getY();
cout << "b = " << mb << endl;
int mc = obj.getZ();
cout << "c = " << mc << endl;
return 0;
}
4.4.3 虚基类
1. 为什么引入虚基类
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
a = 5;
cout << "Base a = " << a << endl;
}
protected:
int a;
};
class Base1 : public Base
{
public:
int b1;
Base1()
{
a = a + 10;
cout << "Base1 a = " << a << endl;
}
};
class Base2 : public Base
{
public:
int b2;
Base2()
{
a = a + 20;
cout << "Base2 a = " << a << endl;
}
};
class Derived : public Base1, public Base2
{
public:
int d;
Derived()
{
cout << "Base1::a = " << Base1::a << endl;
cout << "Base2::a = " << Base2::a << endl;
}
};
int main()
{
//Base1和Base2中继承了Base中的同名数据成员,它们都是Base成员的赋值,但是Base1中和Base2中的
//数据成员a分别具有不同的存储单元,可以存放不同的数据。
//由于类Derived中同时存在类Base1和类Base2中的同名数据成员a,所以输出时
//必须加上“类名::”,来避免二义性,为了解决二义性,所以引入了虚基类的概念
Derived obj;
return 0;
}
2. 虚基类的使用
#include <iostream>
using namespace std;
//使用关键字virtual,把类Base声明为虚基类(在继承的时候声明),这样Base1和Base2派生出的类Derived只继承
//基类Base一次,也就是说,基类Base的成员a只保留一份(继承图P173)
class Base
{
public:
Base()
{
a = 5;
cout << "Base a = " << a << endl;
}
protected:
int a;
};
class Base1 : virtual public Base//virtual声明是虚基类
{
public:
int b1;
Base1()
{
a = a + 10;
cout << "Base1 a = " << a << endl;
}
};
class Base2 : virtual public Base//virtual声明是虚基类
{
public:
int b2;
Base2()
{
a = a + 20;
cout << "Base2 a = " << a << endl;
}
};
class Derived : public Base1, public Base2
{
public:
int d;
Derived()
{
cout << "Derived a = " << a << endl;
}
};
int main()
{
Derived obj;
return 0;
}
3. 虚基类的派生类构造函数的执行顺序
#include <iostream>
using namespace std;
//Base是一个虚基类,它只有一个带参数的构造函数,因此要求在派生类Base1、Base2和Derived的构造函数的初始化表中
//都必须带有对类Base构造函数的调用。
//如果Base不是虚基类,在派生类Derived的构造函数的初始化表中调用类Base的构造函数时错误的,但是当Base是
//虚基类且只有带参数的构造函数时,就必须在类Derived的构造函数的初始化表中调用类Base的构造函数。
//因此,在类Derived构造函数的初始化表中,不仅含有对类Base1和类Base2构造函数的调用,还有对虚基类Base构造函数的调用。
class Base
{
public:
Base(int sa)
{
a = sa;
cout << "Constructing Base." << endl;
}
private:
int a;
};
//可以出现Base基类是一些类的虚基类,是另一些类的非虚基类的情况
class Base1 : virtual public Base//virtual和public的位置可以交换,没有要求
{
public:
Base1(int sa, int sb) : Base(sa)
{
b = sb;
cout << "Constructing Base1" << endl;
}
private:
int b;
};
class Base2 : virtual public Base
{
public:
Base2(int sa, int sc) : Base(sa)
{
c = sc;
cout << "Constructing Base2" << endl;
}
private:
int c;
};
class Derived : public Base1, public Base2
{
public:
Derived(int sa, int sb, int sc, int sd) : Base(sa), Base1(sa, sb), Base2(sa, sc)//这里还需要对非直接基类Base进行构造函数的调用
{
d = sd;
cout << "Constructing Derived" << endl;
}
private:
int d;
};
int main()
{
//虚基类Base的构造函数只执行了一次。显然,当Derived的构造函数调用了虚基类Base的构造函数之后
//类Base1和类Base2对Base构造函数的调用被忽略了。这是初始化虚基类和初始化非虚基类的区别。
Derived obj(2, 4, 6, 8);
return 0;
}
4. 虚基类的简单应用举例
#include <iostream>
#include <string>
using namespace std;
class Data_rec
{
public:
Data_rec(string name1, char sex1, int age1)
{
name = name1;
sex = sex1;
age = age1;
}
protected:
string name;
char sex;
int age;
};
class Student : virtual public Data_rec
{
public:
Student(string name1, char sex1, int age1, string major1, double score1)
: Data_rec(name1, sex1, age1)
{
major = major1;
score = score1;
}
protected:
string major;
double score;
};
class Employee : virtual public Data_rec
{
protected:
string dept;
double salary;
public:
Employee(string name1, char sex1, int age1, string dept1, double salary1)
: Data_rec(name1, sex1, age1)
{
dept = dept1;
salary = salary1;
}
};
class E_Student : public Employee, public Student
{
public:
E_Student(string name1, char sex1, int age1, string major1, double score1, string dept1,
double salary1) : Data_rec(name1, sex1, age1), Student(name1, sex1, age1,
major1, score1), Employee(name1, sex1, age1, dept1, salary1)
{}
void print();
};
void E_Student::print()
{
cout << "name: " << name << endl;
cout << "sex: " << sex << endl;
cout << "age: " << age << endl;
cout << "score: " << score << endl;
cout << "major: " << major << endl;
cout << "dept: " << dept << endl;
cout << "salary: " << salary << endl;
}
int main()
{
E_Student my_E_Student("ZhangDaMing", 'f', 35, "computer science", 95, "Apartment", 3500);
my_E_Student.print();
return 0;
}
4.5 基类和派生类对象之间的赋值兼容关系
基类和派生类对象之间的转换
#include <iostream>
using namespace std;
//总之,只能把派生类赋值给基类,原因是派生类有基类的全部成员,能够完成基类的初始化操作
class Base
{
public:
int i;
Base(int x)
{
i = x;
}
void show()
{
cout << "Base " << i << endl;
}
};
class Derived : public Base
{
public:
Derived(int x) : Base(x)
{}
};
void fun(Base &bb)
{
cout << bb.i << endl;
}
int main()
{
Base b1(100);
b1.show();
Derived d1(11);
b1 = d1;//1.用派生类对象d1给基类对象b1赋值
b1.show();
Derived d2(22);
Base &b2 = d2;//2.用派生类对象d2来初始化基类对象的引用b2
b2.show();
Derived d3(33);
Base *b3 = &d3;//3.用派生类的地址赋值给指向基类对象的指针b3
b3->show();
Derived d4(44);
fun(d4);//4.派生类对象d4作为函数fun形参Base &bb的实参
return 0;
}
4.6 应用举例
例子1——大学管理系统
#include <iostream>
#include <string>
using namespace std;
class Data_rec
{
public:
Data_rec(string name1, char sex1, int age1)
{
name = name1;
sex = sex1;
age = age1;
}
void print()
{
cout << "name: " << name << endl;
cout << "sex: " << sex << endl;
cout << "age: " << age << endl;
}
protected:
string name;
char sex;
int age;
};
class Student : virtual public Data_rec
{
public:
Student(string name1, char sex1, int age1, string major1, double score1) : Data_rec(name1, sex1, age1)
{
major = major1;
score = score1;
}
void print()
{
Data_rec::print();
cout << "score: " << score << endl;
cout << "major: " << major << endl;
}
protected:
string major;
double score;
};
class Employee : virtual public Data_rec
{
public:
Employee(string name1, char sex1, int age1, string dept1, double salary1)
: Data_rec(name1, sex1, age1)
{
dept = dept1;
salary = salary1;
}
void print()
{
Data_rec::print();
cout << "department: " << dept << endl;
cout << "salary: " << salary << endl;
}
protected:
string dept;
double salary;
};
class Teacher : public Employee
{
public:
Teacher(string name1, char sex1, int age1, string dept1, double salary1, string title1)
: Data_rec(name1, sex1, age1), Employee(name1, sex1, age1, dept1, salary1)
{
title = title1;
}
void print()
{
Employee::print();
cout << "title1: " << title << endl;
}
protected:
string title;
};
class E_Student : public Employee, public Student
{
public:
E_Student(string name1, char sex1, int age1, string major1, double score1, string dept1,
double salary1) : Data_rec(name1, sex1, age1), Student(name1, sex1, age1,
major1, score1), Employee(name1, sex1, age1, dept1, salary1)
{}
void print()
{
Student::print();
cout << "departmen: " << dept << endl;
cout << "salary: " << salary << endl;
}
};
int main()
{
Student my_Student("LiXiaoMing", 'f', 22, "Math", 90);
cout << "Student: " << endl;
my_Student.print();
Employee my_Employee("HuangBaiSong", 'm', 55, "Science", 5000);
cout << "Employee: " << endl;
my_Employee.print();
Teacher my_Tercher("WangShiMing", 'm', 50, "Management", 8000, "Professor");
my_Tercher.print();
E_Student my_E_Student("ZhangDaMing", 'm', 35, "computer", 95, "Management", 3500);
cout << "E_Student: " << endl;
my_E_Student.print();
return 0;
}
例子2——楼房
#include <iostream>
using namespace std;
class Building//楼房类基类
{
public:
Building(int f = 0, int r = 0, double a = 0)
{
floors = f;
rooms = r;
j_area = a;
}
void Show_Info()
{
cout << "Buliding's number of floors: " << floors << endl;
cout << "Number of rooms: " << rooms << endl;
cout << "Area: " << j_area << endl;
cout << "Include: ";
}
protected:
int floors;
int rooms;
double j_area;
};
class Home_Arch : public Building//住宅楼作为公有派生类
{
public:
Home_Arch(int f = 0, int r = 0, double a = 0, int b = 1, int p = 1, int to = 1, int k = 1)
: Building(f, r, a)
{
bedrooms = b;
parlor = p;
toilets = to;
kitchens = k;
}
void Show()
{
cout << endl;
cout << "House to Live: "<< endl;
Building::Show_Info();
cout << "Number of bedroom: " << bedrooms << endl;
cout << " Number of parlor: " << parlor << endl;
cout << " Number of toilets: " << toilets << endl;
cout << " Number of kitchens: " << kitchens << endl;
}
private:
int bedrooms;
int parlor;
int toilets;
int kitchens;
};
class Office_Building : public Building
{
public:
Office_Building(int f = 0, int r = 0, double a = 0, int o = 0, int a_r = 0) : Building(f, r, a)
{
Office = o;
assembly_room = a_r;
}
void Show()
{
cout << endl;
cout << "Office Building: " << endl;
Building::Show_Info();
cout << "Number of Office: " << Office << endl;
cout << " Number of assembly_room: " << assembly_room << endl;
}
private:
int Office;
int assembly_room;
};
class Hospital : public Building
{
public:
Hospital(int f = 0, int r = 0, double a = 0, int s = 0, int o = 0) : Building(f, r, a)
{
sickrooms = s;
operating_rooms = o;
}
void Show()
{
cout << endl;
cout << "Hospital: " << endl;
Building::Show_Info();
cout << "Number of sickrooms: " << sickrooms << endl;
cout << " Number of operating_rooms: " << operating_rooms << endl;
}
private:
int sickrooms;
int operating_rooms;
};
int main()
{
Home_Arch home(7, 100, 12000, 3, 1, 1, 1);
Office_Building office(4, 80, 3500, 40, 12);
Hospital hosp(10, 300, 25000, 200, 20);
home.Show();
office.Show();
hosp.Show();
return 0;
}
第5章 多态性
5.1 多态性简介
面向对象方法中,多态性指的是不同对象收到相同的信息时,产生不同的行为(即方法)。C++程序设计中,多态性指的是用一个名字定义不同的函数,这些函数执行不同但是又类似的操作,这样就可以用同一个函数名调用不同内容的函数。从而实现“一个接口,多个方法。”
C++中,多态性的实现和联编(也叫绑定)这一概念有关。一个源程序经过编译、连接,成为可执行文件的过程时把可执行代码联编(或称装配)在一起的过程。其中在运行之前就完成的联编成为静态联编,又叫做前期联编;而在程序运行时才完成的联编叫动态联编,也称后期联编。
静态联编是指系统在编译时就决定如何实现某一动作。静态联编要求在程序编译时就直到调用函数的全部信息。因此,这种联编类型的函数调用速度很快。效率高是静态联编的主要优点。
动态联编是指系统在运行时动态实现某一动作。采用这种联编方式,一直要到程序运行时才能确定调用哪个函数。动态联编的主要优点是:提供了更好的灵活性、问题抽象性和程序易维护性。
静态联编支持的多态性称为编译时多态性,也成为静态多态性。主要通过函数重载(包括运算符重载)和模板实现的。利用函数重载机制,在调用同名的函数时,编译系统可根据实参的具体情况确定所要调用的函数。动态联编所支持的多态性称为运行时多态性,也称为动态多态性。通过虚函数来实现。
5.2 运算符重载
5.2.1 在类外定义的运算符重载函数
#include <iostream>
using namespace std;
//运算符重载函数可以是在类外定义的普通函数,也可以是类的成员函数或友元函数
//绝大部分的运算符都支持重载,不能重载的运算符只有
// 1. '.'成员访问运算符
// 2. '. *'成员指针访问运算符
// 3. "::"作用域运算符
// 4. "sizeof"长度运算符
// 5. "? :"条件运算符
//运算符重载只能重载已有的成员类型,不能用户自己定义新的运算符。
//运算符重载不能改变运算符的操作对象的个数。双目运算符重载后仍然是双目运算符。
//运算符重载不能改变运算符原有的优先级。
//运算符重载不能改变运算符原有的结合特性。
//运算符重载函数的参数至少应该有一个是类对象(或者类对象的引用)。这样是为了防止用户修改用于标准类型数据的运算符性质。例如
//int operator+ (int x, int y)
//{ return x - y }
//一般来说,用于类对象的运算符必须重载,但是赋值运算符"="例外,不必用户进行重载。通常情况下,赋值运算符"="可用于同类对象之间的相互赋值。
//这是因为C++系统已经为每一个新声明的类重载了一个赋值运算符函数,用户不必自己编写运算符重载函数。
//但在某些情况下,例如数据成员中包含指向动态分配内存的指针成员时,使用系统提供的"="会出现可能会出现错误,这时候,就需要用户自己编写赋值运算符重载函数
class Complex
{
public:
double real;
double imag;
Complex(double r = 0, double i = 0)
{
real = r;
imag = i;
}
};
//返回值类型 operator运算符 (参数表)
Complex operator+ (Complex co1, Complex co2)
{
Complex temp;
temp.real = co1.real + co2.real;
temp.imag = co1.imag + co2.imag;
return temp;
}
int main()
{
Complex com1(1.1, 2.2), com2(3.3, 4.4), total1, total2;
total1 = operator+(com1, com2);//调用运算符重载函数operator+的第一种方式
cout << "real1 = " << total1.real << " " << "imag1 = " << total1.imag << endl;
total2 = com1 + com2;//第二种方式,更加符合直觉
cout << "real2 = " << total2.real << " " << "imag2 = " << total2.imag << endl;
return 0;
}
5.2.2 友元运算符重载函数
1. 双目运算符重载
#include <iostream>
using namespace std;
//将运算符重载函数声明为类的友元函数,是为了操作类的私有变量和保护变量
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0);
void print();
friend Complex operator+ (Complex &a, Complex &b);
friend Complex operator- (Complex &a, Complex &b);
friend Complex operator* (Complex &a, Complex &b);
friend Complex operator/ (Complex &a, Complex &b);
private:
double real;
double imag;
};
Complex::Complex(double r, double i)//友元函数不是类的成员函数不需要加上类名和作用域运算符,operator是定义运算符重载函数的关键字。
{
real = r;
imag = i;
}
Complex operator+ (Complex &a, Complex &b)
{
Complex temp;
temp.real = a.real + b.real;
temp.imag = a.imag + b.imag;
return temp;
}
//可以将对'+'号重载的运算符写为
//Complex operator+ (Complex &a, Complex &b)
//{ return Complex(a.real + b.real, a.imag + b.imag) }
//这种方法建立一个临时对象,这个对象没有对象名,是一个无名对象。在建立临时对象过程中调用构造函数,return将临时对象作为函数返回值,这种方法效率更高一点。
Complex operator- (Complex &a, Complex &b)
{
Complex temp;
temp.real = a.real - b.real;
temp.imag = a.imag - b.imag;
return temp;
}
Complex operator* (Complex &a, Complex &b)
{
Complex temp;
temp.real = a.real * b.real - a.imag * b.imag;
temp.imag = a.real * b.imag + a.imag * b.real;
return temp;
}
Complex operator/ (Complex &a, Complex &b)
{
Complex temp;
double t;
t = 1 / (b.real * b.real + b.imag * b.imag);
temp.real = (a.real * b.real + a.imag * b.imag) * t;
temp.imag = (b.real * a.imag - a.real * b.imag) * t;
return temp;
}
void Complex::print()
{
cout << real;
if (imag > 0)
cout << "+";
if (imag != 0)
cout << imag << 'i' << endl;
}
int main()
{
Complex A1(2.3, 4.6), A2(3.6, 2.8), A3, A4, A5, A6;
A3 = A1 + A2;//A3 = operator+(A1, A2);
A4 = A1 - A1;//A4 = operator-(A1, A2);
A5 = A1 * A2;//A5 = operator*(A1, A2);
A6 = A1 / A2;//A6 = operator/(A1, A2);
A1.print();
A2.print();
A3.print();
A4.print();
A5.print();
A6.print();
return 0;
}
2. 单目运算符重载
#include <iostream>
using namespace std;
class Coord
{
public:
Coord(int x1 = 0, int y1 = 0)
{
x = x1;
y = y1;
}
friend Coord operator- (Coord &obj);//声明单目运算符-重载函数
void print();
private:
int x, y;
};
Coord operator- (Coord &obj)//注意,这里一定要传入对象的引用,如果这里是重载"++"自增运算符,不传入对象引用会出现错误
{
obj.x = -obj.x;
obj.y = -obj.y;
return obj;
}
void Coord::print()
{
cout << "x = " << x << endl << "y = " << y << endl;
}
int main()
{
Coord ob1(50, 60), ob2;
ob1.print();
ob2 = -ob1;
ob2.print();
return 0;
}
//有写运算符不能定义为友元运算符重载函数,如赋值运算符“=”、下标运算符“[]”、函数调用运算符“()”等。
5.2.3 成员运算符重载函数
1. 双目运算符重载
#include <iostream>
using namespace std;
//使用成员运算符重载函数是为了操作私有和保护成员
//需要注意的是,在成员运算符重载的形参表中,若运算符是单目的,则参数表为空;若运算符是双目的,则参数表中有一个操作数。
//另外一个操作数(左操作数)是隐藏的,是该类的对象,它通过this指针隐含地传递给函数
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0);
void print();
Complex operator- (Complex c);
Complex operator+ (Complex c);
Complex operator* (Complex c);
Complex operator/ (Complex c);
private:
double real;
double imag;
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
//我们可以发现,将复数的四则运算符重载为类的成员函数,除了声明以及定义的时候使用了关键字operator之外,成员运算符重载函数和普通的类成员函数
//没有过多的区别。对复数类重载了之后,就如同基本的数据类型的运算一样。
Complex Complex::operator+ (Complex c)
{
Complex temp;
temp.real = real + c.real;
temp.imag = imag + c.imag;
return temp;
}
Complex Complex::operator- (Complex c)
{
Complex temp;
temp.real = real - c.real;
temp.imag = imag - c.imag;
return temp;
}
Complex Complex::operator* (Complex c)
{
Complex temp;
temp.real = real * c.real - imag * c.imag;
temp.imag = real * c.imag + imag * c.real;
return temp;
}
Complex Complex::operator/ (Complex c)
{
Complex temp;
double t;
t = 1 / (c.real * c.real + c.imag * c.imag);
temp.real = (real * c.real + imag * c.imag) * t;
temp.imag = (c.real * imag - real * c.imag) * t;
return temp;
}
void Complex::print()
{
cout << real;
if (imag > 0)
cout << "+";
if (imag != 0)
cout << imag << 'i' << endl;
}
int main()
{
Complex A1(2.3, 4.6), A2(3.6, 2.8), A3, A4, A5, A6;
A3 = A1 + A2;//A3 = A1.operator+ (A2); //另一个操作数是通过this指针,将调用函数的对象传入隐式传递
A4 = A1 - A2;//A4 = A1.operator- (A2);
A5 = A1 * A2;//A5 = A1.operator* (A2);
A6 = A1 / A2;//A6 = A1.operator/ (A2);
A1.print();
A2.print();
A3.print();
A4.print();
A5.print();
A6.print();
return 0;
}
2. 单目运算符重载
#include <iostream>
using namespace std;
class Coord
{
public:
Coord(int i = 0, int j = 0);
void print();
Coord operator++ ();//声明运算符++重载函数operator++
private:
int x, y;
};
Coord::Coord(int i, int j)
{
x = i;
y = j;
}
void Coord::print()
{
cout << "x: " << x << ", y: " << y << endl;
}
Coord Coord::operator++ ()//定义运算符重载函数operator++
{
++x;
++y;
return *this;//返回当前对象的值
}
int main()
{
Coord ob(10, 20);
ob.print();
++ob;//隐式地调用运算符重载函数++
ob.print();
ob.operator++();//显式地调用运算符重载函数++
ob.print();
return 0;
}
//成员运算符重载函数和友元运算符重载函数相比较
//1.参数表中参数的个数不同,成员运算符重载函数参数隐式地使用了this指针,所以参数少一个。
//2.双目运算符一般可以被重载为友元运算符重载函数或成员运算符重载函数,但是有一种情况,必须声明为友元函数
// 即,如果需要将一个标准类型和类对象做运算
// Complex operator+ (int a)
// { return (real + a, imag) }
// 使用成员运算符重载函数如果语句为com1 = com + 100; 则被解释为com1 = com.operator+(100)
// 但是如果语句为com1 = 100 + com; 语句就被解释为com1 = 100.operator+(com); 然而100,并不是一个对象,不能调用重载的成员运算符函数
// 具体的使用友元运算符重载函数实现的例子请看同根目录下的例子
//3.成员运算符函数和友元运算符函数可以根据习惯方式调用,也能够通过专用的方式调用
// 习惯调用方式 友元运算符重载函数调用形式 成员运算符重载函数调用形式
// a + b operator+(a, b) a.operator+(b)
// -a operator-(a) a.operator-()
// a++ operator++(a, 0) a.operator++(0)
//4.C++大部分的运算符既可以说明为成员运算符重载函数,也能够说明为友元运算符重载函数。可以根据实际情况和习惯选择使用。
// 一般而言,对于双目运算符,重载为友元运算符重载函数比重载为成员运算符重载函数便于使用。但是对于单目运算符,则选择成员运算符较好。
// 如果运算符所需要的操作数(尤其是第一个操作数)希望有隐式类型转换,则运算符重载必须使用友元函数,而不能使用成员函数。
// ·对于单目运算符,建议使用成员函数;
// ·对于运算符" = , () , [] , -> "只能作为成员函数;
// ·对于运算符" += , -= , /= , *= , &= , != , ~= , %= , <<= , >>= ",建议重载为成员函数
// ·对于其他运算符,建议重载为友元函数
3. 友元运算符重载相较于成员运算符重载更好的地方
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int real1 = 0, int imag1 = 0);
//需要定义两次
friend Complex operator+ (Complex com, int a)//+号左侧是对象,右侧是标准类型
{
return Complex(com.real + a, com.imag);
}
friend Complex operator+ (int a, Complex com)//+号左侧式标准类型,右侧是对象
{
return Complex(a + com.real, com.imag);
}
void show();
private:
int real, imag;
};
Complex::Complex(int real1, int imag1)
{
real = real1;
imag = imag1;
}
void Complex::show()
{
cout << "real = " << real << " imag = " << imag << endl;
}
int main()
{
Complex com1(30, 40), com2;
com2 = com1 + 30;
com2.show();
com2 = 50 + com1;
com2.show();
return 0;
}
5.2.4 特殊运算符的重载
1. “++”和“--”的重载
成员函数运算符重载
#include <iostream>
using namespace std;
class Three
{
public:
Three(int I1 = 0, int I2 = 0, int I3 = 0);
void print();
//++,--操作符均有前缀、后缀两种方式,使用int关键字来区分这两种方式,后缀方式多了int参数,显式调用一般传入参数0
Three operator-- ();//前缀方式
Three operator-- (int);//后缀方式
private:
int i1, i2, i3;
};
Three::Three(int I1, int I2, int I3)
{
i1 = I1;
i2 = I2;
i3 = I3;
}
void Three::print()
{
cout << "i1: " << i1 << " i2: " << i2 << " i3: " << i3 << endl;
}
Three Three::operator--()//定义自减运算符--重载成员函数(前缀方式)
{
--i1;
--i2;
--i3;
return *this;//返回自减后的对象
}
Three Three::operator-- (int)//定义自减运算符--重载成员函数(后缀方式)
{
Three temp(*this);
i1--;
i2--;
i3--;
return temp;//返回自减前的对象
}
int main()
{
Three obj1(4, 5, 6), obj2, obj3(11, 12, 13), obj4;
obj1.print();
--obj1;
obj1.print();
obj2 = obj1--;
obj2.print();
obj1.print();
cout << endl;
obj3.print();
obj3.operator--();//前缀方式,显式调用
obj3.print();
obj4 = obj3.operator--(0);//后缀方式,显式调用
obj4.print();
obj3.print();
return 0;
}
友元函数运算符重载
#include <iostream>
using namespace std;
class Three
{
public:
Three(int I1, int I2, int I3);
void print();
friend Three operator++(Three &);//声明自加运算符++重载友元函数(前缀方式)
friend Three operator++(Three &, int);//声明自家运算符++重载友元函数(后缀方式),多了个int关键字
private:
int i1, i2, i3;
};
Three::Three(int I1, int I2, int I3)
{
i1 = I1;
i2 = I2;
i3 = I3;
}
void Three::print()
{
cout << "i1: " << i1 << " i2: " << i2 << " i3: " << i3 << endl;
}
//注意和成员函数的区别,友元函数没有this指针,无法引用this指针所指的对象,所以应该采用对象引用传递参数
Three operator++(Three &op)//定义前缀方式
{
++op.i1;
++op.i2;
++op.i3;
return op;
}
Three operator++(Three &op, int)//定义后缀方式,注意这里int根本没有定义形参,实参传进来就丢掉了
{
op.i1++;
op.i2++;
op.i3++;
return op;
}
int main()
{
Three obj1(4, 5, 6), obj2(14, 15, 16);
obj1.print();
++obj1;
obj1.print();
obj1++;
obj1.print();
cout << endl;
obj2.print();
operator++(obj2);
obj2.print();
operator++(obj2, 0);
obj2.print();
return 0;
}
2. 赋值运算符“=”的重载
认识指针悬挂问题
#include <iostream>
#include <cstring>
using namespace std;
//一般来说,用户如果没有自定义复制运算符函数,那么系统将会自动地生成一个默认的运算符函数
// X &X::operator= (const X &source)
// {
// //成员间赋值
// }
//一般来说这个函数是能够满足要求的,但是如果类内部有指针的话,可能会出现指针悬挂的问题。
class STRING
{
public:
STRING(char *s)
{
cout << "Constructor called." << endl;
ptr = new char[strlen(s + 1)];
strcpy(ptr, s);
}
~STRING()
{
cout << "Destructor called.---" << ptr << endl;
delete ptr;
}
private:
char *ptr;
};
int main()
{
STRING p1("book");
STRING p2("jeep");
p2 = p1;
//顺序,先析构p2,再析构p1
//程序运行结果会发生p1析构函数打印ptr错误,这是因为,p2的析构函数已经把指向"book"那块地址释放掉了,而p1仍然指向那块地址
//之后调用p1的析构函数会产生对同一块地址的两次释放,因此产生了错误(p1指向的字符串会被随机字符取代)
//因为调用的是默认的赋值运算符函数,采用的是浅层复制的方式,使得两个对象p1和p2指针指向new开辟的同一个空间,这就是“指针悬挂“现象
return 0;
}
深层复制解决指针悬挂问题
#include <iostream>
#include <cstring>
using namespace std;
class STRING
{
public:
STRING(char *s)
{
cout << "Constructor called." << endl;
ptr = new char[strlen(s + 1)];
strcpy(ptr, s);
}
~STRING()
{
cout << "Destructor called.---" << ptr << endl;
delete ptr;
}
STRING &operator= (const STRING &);//声明,注意返回的是引用
private:
char *ptr;
};
STRING &STRING::operator= (const STRING &s)//定义,注意返回的是引用
{
if (this == &s)
return *this;//防止s=s赋值
delete ptr;//释放掉原来区域
ptr = new char[strlen(s.ptr + 1)];//分配新区域
strcpy(ptr, s.ptr);//字符串复制
return *this;
}
int main()
{
STRING p1("book");
STRING p2("jeep");
p2 = p1;
return 0;
}
//赋值运算符“=”只能重载为成员函数,而不能把它重载为友元函数,因为如果重载为友元函数
// friend string &operator= (string &p2, string &p1)
// 表达式p1 = "book";会被解释为operator(p1, "book");这是没有什么问题的
// 但是对于表达式"book" = p1;会被解释为operator("book", p1)
// C++编译器首先将"book"转换为一个隐藏的string对象,然后使用对象p2引用该隐藏对象,并不认为这个表达式是错误的从而倒是赋值语句上的混乱。
// 因此双目赋值运算符应该重载为成员函数的形式,而不能重载为友元函数的形式。
3. 下标运算符“[]”的重载
引例
#include <iostream>
using namespace std;
class Vector4
{
public:
Vector4(int a1, int a2, int a3, int a4)
{
v[0] = a1;
v[1] = a2;
v[2] = a3;
v[3] = a4;
}
private:
int v[4];
};
int main()
{
Vector4 v(1, 2, 3 ,4);
//cout << v[2];
//上面的语句是非法的,因为v[2]是类Vector的私有数据,即使修改为公有数据,也应该是cout << ve.v[2];
//如果对[]运算符进行重载,使得它具有输出向量分量(即数据元素)的功能,则原输出语句cout << v[2];是合法有效的
return 0;
}
通过重载改进引例
#include <iostream>
using namespace std;
class Vector4
{
public:
Vector4(int a1, int a2, int a3, int a4)
{
v[0] = a1;
v[1] = a2;
v[2] = a3;
v[3] = a4;
}
int& operator[] (int bi);//声明下标运算符[]重载函数
private:
int v[4];
};
int &Vector4::operator[] (int bi)//返回一个int型的引用,可以使得重载的"[]"可以在赋值语句的左边,
//因而在main函数种,v[2]可以出现在赋值运算符的任何一遍,使得编制程序更加了灵活
{
if (bi < 0 || bi >= 4)
{
cout << "Bad subscript!" << endl;
exit(1);
}
return v[bi];
}
int main()
{
Vector4 v(1, 2, 3 ,4);
cout << v[2] << endl;
v[3] = v[2];
cout << v[3] << endl;
v[2] = 22;
cout << v[2];
return 0;
}
5.3 类型转换
5.3.1 系统预定义类型的转换
一、隐式类型转换
1.在赋值运算符A = B的情况下,赋值运算符右端B的值需转换为A类型后进行赋值。
2.当cahr或者short类型变量与int类型变量进行运算时,将char和short类型变量转换为int类型。
3.当两个操作对象类型不一致时,在算术运算前,级别低的类型自动转换为级别高的类型。
二、显式类型转换
C++中提倡的方式为cout << int(i + j),因为这样更像是一个函数,但是同时C++中也保留了C语言的用法cout << (int)(i + j)。
5.3.2 类类型与系统预定义类型见的转换
1. 转换构造函数
#include <iostream>
using namespace std;
//转换构造函数是构造函数的一种,它具有类型转换的作用,它的作用是将一个其他类型的数据
//转换为它所在类的对象。通常,转换构造函数只有一个参数,参数是待转换类型的数据。
//转换构造函数只有一个参数,但是只有一个参数的构造函数不一定是转换构造函数,它也有可能是普通的构造函数。
class Complex
{
public:
Complex() {};
Complex(double r, double i)
{
real = r;
imag = i;
}
Complex(double r)//声明转换构造函数
{
real = r;
}
friend Complex operator+ (Complex &co1, Complex &co2);
void print();
private:
double real, imag;
};
Complex operator+ (Complex &co1, Complex &co2)
{
Complex temp;
temp.real = co1.real + co2.real;
temp.imag = co1.imag + co2.imag;
return temp;
}
void Complex::print()
{
cout << real;
if (imag > 0)
cout << "+";
if (imag != 0)
cout << imag << "i" << endl;
}
int main()
{
Complex com1(1.1, 2.2), total;
Complex com2(7.7);
total = com1 + com2;
total.print();
return 0;
}
2. 类型转换函数
类型转换函数1
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(double r = 0, double i = 0)
{
real = r;
imag = i;
}
operator double()//类型转换函数没有返回类型和参数,函数名称为operator double
//类型转换函数,将一个Complex类的对象转换为一个double类型的数据,目标类型可以是标准类型或类类型
//但是不能反过来将double类型转换为Complex类的对象
{
return real;
}
private:
double real, imag;
};
int main()
{
Complex com(2.2, 4.4);
cout << double(com) << endl;
return 0;
}
//类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。
//类型转换函数没有参数,也不能在函数名前面指定函数类型
//类型函数中必须有return语句,即必须送回目标类型的数据作为函数的返回值
//一个类可以定义多个类型转换函数。C++编译器将根据类型转换函数名自动地选择一个合适的类型转换函数调用。
类型转换函数2
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(double r = 0, double i = 0)
{
real = r;
imag = i;
}
operator double()
{
return real;
}
operator int()
{
return int(real);
}
private:
double real, imag;
};
int main()
{
Complex com1(22.2, 4.4);
cout << double(com1) << endl;
Complex com2(66.6, 4.4);
cout << int(com2) << endl;
return 0;
}
3. 综合运用
#include <iostream>
using namespace std;
class Complex
{
public:
Complex() {}
Complex(int r, int i)
{
real = r;
imag = i;
}
Complex(int i)//转换构造函数,将int型数据转换称Complex类型数据
{
real = imag = i / 2;
}
operator int()//类型转换函数,将Complex类型的数据转换为int型数据
{
return real + imag;
}
void print()
{
cout << "real: " << real << "\t" << "imag: " << imag << endl;
}
private:
int real, imag;
};
int main()
{
Complex a1(1, 2), a2(3, 4);
Complex a3;
a3 = a1 + a2;
a3.print();
return 0;
}
//这个程序奇怪的地方就是我们并没有重载+运算符,但是仍然使用了+号进行运算,这是因为C++自动进行了隐式转换,步骤如下
//1.寻找将两个Complex类对象相加的运算符重载函数,程序中未找到。
//2.寻找能将Complex类对象转换成int型数据的类型转换函数operator int(),程序中找到,于是隐式调用将
// a1和a2隐式转换成int型数据3和7。
//3.寻找将两个整数相加的运算符函数,这个运算符函数已经在C++系统中预定义。于是就调用。
//4.由于语句“a3 = a1 + a2”语句赋值号左边是Complex类对象a3,而右边是int类型数据10,于是隐式调用
// 转换构造函数将int型数10转换成Complex类的一个临时对象,然后将这个临时对象的值赋值给a3,使得a3的
// real和imag均为5
5.4 虚函数
5.4.1 虚函数的引入
1. 为什么要引入虚函数
#include <iostream>
using namespace std;
class My_base
{
public:
My_base(int x, int y)
{
a = x;
b = y;
}
void show()
{
cout << "My_base show: ";
cout << a << " " << b << endl;
}
private:
int a, b;
};
class My_class : public My_base
{
public:
My_class(int x, int y, int z) : My_base(x, y)
{
c = z;
}
void show()
{
cout << "My_class show: ";
cout << "c = " << endl;
}
private:
int c;
};
int main()
{
My_base mb(50, 50), *mp;
My_class mc(10, 20, 30);
mp = &mb;
mp->show();
mp = &mc;
mp->show();
return 0;
}
//从结果来看,虽然第二次将基类指针mp指向了派生类对象mc,但是执行语句“mp->show()”调用的不是派生类
//的成员函数show,而仍然是基类的同名成员函数show。
//原来C++中规定:基类的对象指针可以指向它的公有派生的对象,但是当指向公有派生类对象时,它只能访问派生类
//中从基类继承来的成员,而不能访问公有派生类中定义的成员
//但是我们在这里使用对象指针的目的是为了表达一种动态的性质,即当指针指向不同对象(基类对象或派生类对象)时,
//分别调用不同类的成员函数。如果将函数说明为虚函数,就能实现这种动态调用的功能。
2. 引入虚函数
#include <iostream>
using namespace std;
class My_base
{
public:
My_base(int x, int y)
{
a = x;
b = y;
}
virtual void show()//基类中定义虚函数show
{
cout << "My_base show: ";
cout << a << " " << b << endl;
}
private:
int a, b;
};
class My_class : public My_base
{
public:
My_class(int x, int y, int z) : My_base(x, y)
{
c = z;
}
virtual void show()//派生类中重新定义虚函数show
{
cout << "My_class show: ";
cout << "c = " << endl;
}
private:
int c;
};
int main()
{
My_base mb(50, 50), *mp;
My_class mc(10, 20, 30);
mp = &mb;
mp->show();
mp = &mc;
mp->show();
return 0;
}
//我们将基类和派生类中的show函数均声明为virtual虚函数时,调用结果就正确了
//这是因为关键字virtual指示C++编译器,函数调用my->show()要在运行时确定所要调用的函数,即要对该调用进行动态联编
//因此,程序在运行时根据指针mp所指向的实际对象,调用该对象的成员函数。
5.4.2 虚函数的定义
1. 虚函数的定义
#include <iostream>
using namespace std;
//虚函数就是在基类中被关键字virtual说明,并在派生类中重新定义的函数。
//虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中同名函数。
//虚函数的定义是在基类中进行的,它是在基类中需要定义为虚函数的成员函数的声明中返回值类型前加上virtual
//基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义。在派生类中重新定义时
//其函数原型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同(是为了实现重载)
//C++规定,当一个成员函数被定义为虚函数后,其派生类中符合重新定义虚函数要求的同名函数都自动称为虚函数
//因此,派生类中重新定义该虚函数时,关键字virtual可写可不写。但是,为了程序清晰,最好每个虚函数都写上。
//如果在派生类中没有对基类的虚函数重新定义,则公有派生类直接继承基类的虚函数。一个虚函数无论被公有继承
//多少次,它仍然保持虚函数的特性。
//虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特性的对象
//来决定该激活哪个函数。
//虽然使用对象名和点运算符的方式也能调用虚函数,但是这种调用实在编译时进行的,是静态联编,没有利用
//虚函数的特性。只有通过基类指针访问虚函数时才能获得运行时的多态性。
class B0
{
public:
virtual void print(char *p)//只需要在类中声明的时候加上virtual,类外定义不需要加virtual
{
cout << p << "print()" << endl;
}
};
class B1 : public B0
{
public:
virtual void print(char *p)
{
cout << p << "print()" << endl;
}
};
class B2 : public B1
{
public:
virtual void print(char *p)
{
cout << p << "print()" << endl;
}
};
int main()
{
B0 ob0, *op;
op = &ob0;
op->print("B0::");
B1 ob1;
op = &ob1;
op->print("B1::");
B2 ob2;
op = &ob2;
op->print("B2::");
return 0;
}
2. 虚析构函数
引例1
#include <iostream>
using namespace std;
class B
{
public:
~B()
{
cout << "B's destructor called." << endl;
}
};
class D : public B
{
public:
~D()
{
cout << "D's destructor called." << endl;
}
};
int main()
{
D obj;
return 0;
}
//本程序的结果时符合预期的,先调用派生类的析构函数,再调用基类的析构函数。
//但是,如果在主函数中用new运算符建立一个派生类的无名对象和定义了一个基类的对象指针,并将无名对象
//的地址赋给这个对象指针。当用delete运算符撤销无名对象的时候,系统只调用基类的析构函数,而不执行派生类的析构函数。
//详情见引例2的运行结果。
引例2
#include <iostream>
using namespace std;
class B
{
public:
~B()
{
cout << "B's destructor called." << endl;
}
};
class D : public B
{
public:
~D()
{
cout << "D's destructor called." << endl;
}
};
int main()
{
B *p;
p = new D;
delete p;
return 0;
}
//结果表明,程序只运行了基类B的析构函数,而没有执行派生类D的析构函数,原因是当撤销指针P所指的派生类的无名对象
//而调用析构函数时,采用了静态联编的方式,只调用了基类B的析构函数。
//如果希望程序执行动态联编的方式,在用delete运算符撤销派生类的无名对象时,先调用派生类的析构函数
//再调用基类的析构函数,可以将基类的析构函数声明为虚析构函数
虚析构函数的使用
#include <iostream>
using namespace std;
class B
{
public:
virtual ~B()//声明为虚析构函数
{
cout << "B's destructor called." << endl;
}
};
class D : public B
{
public:
virtual ~D()//声明为虚析构函数
{
cout << "D's destructor called." << endl;
}
};
int main()
{
B *p;
p = new D;
delete p;
return 0;
}
//这时,程序的结果符合我们的愿望,由于使用了虚析构函数,程序执行了动态联编,实现了运行的多态性。
//虽然派生类的析构函数和基类的析构函数名字不相同,但是如果将基类的析构函数定义为虚函数
//由该基类所派生的所有派生类的析构函数也都自动称为虚函数。
3. 虚函数与重载函数的关系
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func1();
virtual void func2();
virtual void func3();
void func4();
};
class Derived : public Base
{
public:
virtual void func1();//func1是虚函数,这里可不写virtual
void func2(int x);//与基类中的func2作为普通函数重载,虚特性消失
// char func3();//错误,与基类中的func2只有返回类型不同,应该删除
void func4();//与基类中的func4是普通函数重载,不是虚函数
};
void Base::func1()
{
cout << "--Base func1--" << endl;
}
void Base::func2()
{
cout << "--Base func2--" << endl;
}
void Base::func3()
{
cout << "--Base func3--" << endl;
}
void Base::func4()
{
cout << "--Base func4--" << endl;
}
void Derived::func1()
{
cout << "--Derived func1--" << endl;
}
void Derived::func2(int x)
{
cout << "--Derived func2--" << endl;
}
void Derived::func4()
{
cout << "--Derived func4--" << endl;
}
int main()
{
Base d1, *bp;
Derived d2;
bp = &d2;
bp->func1();//func1是虚函数
bp->func2();//func2失去了虚函数特性,只是个普通的重载函数
bp->func4();//func4是一个普通的成员函数
return 0;
}
4. 多重继承和虚函数
#include <iostream>
using namespace std;
class Base1
{
public:
virtual void fun()
{
cout << "--Base1--" << endl;
}
};
class Base2
{
public:
void fun()
{
cout << "--Base2--" << endl;
}
};
class Derived : public Base1, public Base2
{
public:
void fun()
{
cout << "--Derived--" << endl;
}
};
int main()
{
Base1 *ptr1;
Base2 *ptr2;
Derived obj3;
ptr1 = &obj3;
ptr1->fun();
ptr2 = &obj3;
ptr2->fun();
return 0;
}
//从结果可以看出,由于派生类Derived中的函数fun有不同的继承路径,所以呈现不同的性质。
//相对于Base1的派生路径,由于Base1中的fun是虚函数,当声明为指向Base1的指针指向派生类Derived的对象obj3时,
//函数fun呈现出虚特性。因此,此时的ptr->fun()调用的时Derived::fun()函数;相对于Base2的派生路径,
//由于Base2中的fun是一般成员函数,所以此时它只能是一个普通的重载函数,当声明为指向Base2的指针
//指向Derived的对象obj3时,函数fun只呈现出普通函数的重载特性。因此,此时的ptr->fun()调用的是
//函数Base2::fun。
5. 虚函数举例
#include <iostream>
using namespace std;
//以下程序中,由于在公共基类Figure中定义了一个虚函数area作为界面接口
//在三个派生类Triangle、Square和Circle中重新定义了虚函数area,分别用于计算三角形、矩形和圆形的面积
//由于p是基类的对象指针,用同一种调用形式"p->area()",就可以调用同一类族中不同类的虚函数。
//这就是多态性,对同一消息,不同的对象有不同的响应方式。
class Figure
{
public:
Figure(double a, double b)
{
x = a, y = b;
}
virtual void area()
{
cout << "在基类中定义的虚函数,为派生类提供一个公共接口,以便派生类根据需要重新定义虚函数。";
}
protected:
double x, y;
};
class Triangle : public Figure
{
public:
Triangle(double a, double b) : Figure(a, b)
{};
void area()
{
cout << "The height of Triangle is " << x << ", bottom is " << y;
cout << ", Area is " << 0.5 * x * y << endl;
}
};
class Square : public Figure
{
public:
Square(double a, double b) : Figure(a, b)
{};
void area()
{
cout << "The length of Square is " << x << ", width is " << y;
cout << ", area is " << x * y << endl;
}
};
class Circle : public Figure
{
public:
Circle(double a) : Figure(a, a)
{};
void area()
{
cout << "The radius of Circle is " << x;
cout << ", area is " << 3.1416 * x * x << endl;
}
};
int main()
{
Figure *p;
Triangle t(10.0, 6.0);
Square s(10.0, 6.0);
Circle c(10.0);
p = &t;
p->area();
p = &s;
p->area();
p = &c;
p->area();
return 0;
}
5.4.3 纯虚函数和抽象类
#include <iostream>
using namespace std;
//有时候,基类往往表示一种抽象的概念,它并不与具体的事物相联系。
//这个程序中的Figure体现了一个抽象的概念,在Figure中定义一个求面积的函数显然是没有意义的。
//但是我们可以将其说明为虚函数,为它的派生类提供一个公共的界面,各派生类根据所表示的图形的不同重新定义这些虚函数
//以提供求面积的各自版本,为此,C++引入了纯虚函数的概念。
//纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派生类中根据需要对它进行定义,或仍然说明为纯虚函数
//声明纯虚函数的一般形式virtual 函数类型 函数名(参数表) = 0
//只是比一般的虚函数多了一个“= 0”。声明为纯虚函数之后,基类就不再给出函数的实现部分。
//纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行重新定义。纯虚函数没有函数体,它最后面的“=0”并不表示
//函数的返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。纯虚函数不具备函数的功能,不能被调用。
//如果一个类至少有一个纯虚函数,那么就称这个类为抽象类。下面是对抽象类的几点规定。
//1.由于抽象类中至少包含一个没有定义功能的纯虚函数,因此抽象类只能用作其他类的基类,不能建立抽象类对象。
//2.抽象类不能用作参数类型、函数返回类型或显式类型转换的类型。但可以声明指向抽象类的指针变量,此指针可以指向它的派生类,进而实现多态性。
//3.如果在抽象类的派生类中没有重新说明纯虚函数,则该函数在派生类中仍然为纯虚函数,而这个派生类仍然还是一个抽象类。
class Circle
{
public:
void setr(int x)
{
r = x;
}
virtual void show() = 0;
protected:
int r;
};
class Area : public Circle
{
public:
void show()
{
cout << "The area of Circle is : " << 3.14 * r * r << endl;
}
};
class Perimeter : public Circle
{
public:
void show()
{
cout << "The perimeter of Circle is : " << 3.14 * 2 * r << endl;
}
};
int main()
{
Circle *ptr;
Area ob1;
Perimeter ob2;
ob1.setr(10);
ob2.setr(10);
ptr = &ob1;
ptr->show();
ptr = &ob2;
ptr->show();
return 0;
}
5.5 应用举例
#include <iostream>
using namespace std;
class Shape
{
public:
Shape(double x)
{
r = x;
}
virtual void area() = 0;//纯虚函数
virtual void perimeter() = 0;//纯虚函数
protected:
double r;
};
class Circle : public Shape
{
public:
Circle(double x) : Shape(x)
{}
void area()
{
cout << "The area of Circle is " << 3.14 * r * r << endl;
}
void perimeter()
{
cout << "The perimeter of Circle is " << 3.14 * r * 2 << endl;
}
};
class In_square : public Shape//声明一个圆内接正方形类
{
public:
In_square(double x) : Shape(x)
{}
void area()
{
cout << "The area of In_square is " << 2 * r * r << endl;
}
void perimeter()
{
cout << "The perimeter of In_square is " << 4 * 1.414 * r << endl;
}
};
class Ex_square : public Shape
{
public:
Ex_square(double x) : Shape(x)
{}
void area()
{
cout << "The area of Ex_square is " << 4 * r * r << endl;
}
void perimeter()
{
cout << "The perimeter of Ex_square is " << 8 * r << endl;
}
};
int main()
{
Shape *ptr;
Circle ob1(5);
In_square ob2(5);
Ex_square ob3(5);
ptr = &ob1;
ptr->area();
ptr->perimeter();
ptr = &ob2;
ptr->area();
ptr->perimeter();
ptr = &ob3;
ptr->area();
ptr->perimeter();
return 0;
}
第6章 模板与异常处理
6.1 模板的概念
模板机制可以显著减少冗余信息,能够大幅度地节约程序代码,进一步提高面向对象程序设计的可重用性和可维护性。模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了代码的重用,使得一段程序可以用于处理多种不同类型的对象,大幅度提高程序设计的效率。
模板分为函数模板和类模板,分别允许用户构造模板函数和模板类。
6.2 函数模板与模板函数
6.2.1 函数模板使用举例1
#include <iostream>
using namespace std;
//函数模板实际上是建立一个通用函数,其函数的返回类型和形参不具体指定,用一个虚拟的类型来代表。这个通用函数就叫做函数模板。
//在调用函数时系统会根据实参的类型(模板实参)来取代模板中虚拟类型从而实现了不同函数的功能。
//格式如下:
// template<typename 类型参数>
// 返回类型 函数名(模板形参表)
// {
// 函数体
// }
//template是一个声明模板的关键字,它表示一个模板。类型参数实际上是一个虚拟的类型名,现在并未指定它时哪一种具体的类型,但使用函数模板时
//必须将类型参数实例化。类型参数前需要加关键字typename或者class(推荐用typename),这样不容易和类混淆。
//函数模板和模板函数的关系是,函数模板经过实例化而生成的具体函数称为模板函数。函数模板代表了一类函数,模板函数表示某一具体的函数。
template<typename AT>
AT Max(AT x, AT y)
{
return (x > y) ? x : y;
}
int main()
{
int i1 = 10, i2 = 56;
double d1 = 50.344, d2 = 4656.346;
char c1 = 'k', c2 = 'n';
cout << Max(i1, i2) << endl;
cout << Max(d1, d2) << endl;
cout << Max(c1, c2) << endl;
return 0;
}
6.2.2 函数模板使用举例2
#include <iostream>
using namespace std;
template<typename T>
T sum(T *array, int size = 0)
{
T total = 0;
for (int i = 0; i < size; i++)
total += array[i];
return total;
}
int int_array[] = {1, 2, 3, 4, 5};
double double_array[] = {1.1, 2.2, 3.3, 4.4, 5.5};
int main()
{
int itotal = sum(int_array, 5);
double dtotal = sum(double_array, 5);
cout << itotal << endl;
cout << dtotal << endl;
return 0;
}
6.2.3 有两个类型参数的函数模板举例
#include <iostream>
using namespace std;
//函数模板中允许使用多个类型参数,但是每个类型参数前,都应该有typename(或class)关键字
//template语句和函数声明之间不允许有别的语句
//模板函数类似于重载函数,但是更加严格,实例化后的模板函数函数体内必须执行相同的操作。
template<typename type1, typename type2>
void myfunc(type1 x, type2 y)
{
cout << x << ' ' << y << endl;
}
int main()
{
myfunc(10, "hao");
myfunc(0.123, 10L);
return 0;
}
6.2.4 函数模板的重载
#include <iostream>
using namespace std;
template<typename Type>
Type Max(Type x, Type y)
{
return (x > y) ? x : y;
}
template<typename Type>
Type Max(Type x, Type y, Type z)
{
Type t;
t = (x > y) ? x : y;
return (t > z) ? t : z;
}
int main()
{
int m = 10, n = 20, Max2;
double a = 10.1, b = 20.2, c = 30.3, Max3;
Max2 = Max(m, n);
Max3 = Max(a, b, c);
cout << "Max(" << m << "," << n << ")=" << Max2 << endl;
cout << "Max(" << a << "," << b << "," << c << ")=" << Max3 << endl;
return 0;
}
6.2.5 函数模板与非模板函数重载
#include <iostream>
using namespace std;
//函数模板和同名的非模板函数可以重载。这种情况下,调用的顺序是:首先寻找一个参数完全匹配的非模板函数,如果找到了就调用它;如果没找到
//就选找函数模板,将其实例化,产生一个与之相匹配的模板函数,若找到了,就调用它。
template<typename AT>
AT Max(AT x, AT y)
{
cout << "template function: ";
return (x > y) ? x : y;
}
int Max(int x, int y)
{
cout << "Not template function: ";
return (x > y) ? x : y;
}
int main()
{
int i1 = 10, i2 = 56;
double d1 = 50.34, d2 = 4656.34;
char c1 = 'k', c2 = 'n';
cout << Max(i1, i2) << endl;
cout << Max(d1, d2) << endl;
cout << Max(c1, c2) << endl;
return 0;
}
6.3 类模板与模板类
6.3.1 类模板Compare的使用举例
#include <iostream>
using namespace std;
template<typename T>
class Compare
{
public:
Compare(T a, T b)
{
x = a;
y = b;
}
T Max()
{
return (x > y) ? x : y;
}
private:
T x, y;
};
int main()
{
Compare<int> com1(3, 7);
Compare<double> com2(12.34, 56.78);
Compare<char> com3('a', 'x');
cout << com1.Max() << endl;
cout << com2.Max() << endl;
cout << com3.Max() << endl;
return 0;
}
//在这个例子中,成员函数(其中含有类型参数)是定义在类模板体内的。如果类模板的成员函数在类模板体外定义,此时有一些特殊规定
//1.需要在成员函数定义之前进行模板声明
//2.在成员函数名前缀上“类名<类型参数>::”
//template<typename 类型参数>
//函数类型 类名<类型参数>::成员函数名(形参表)
// {
// 函数体
// }
//template<typename T>
// T compare<T>::Max()
// {
// return (x > y) ? x : y;
// }
6.3.2 在类模板外定义成员函数
#include <iostream>
using namespace std;
template<typename T>
class Compare
{
public:
Compare(T a, T b);
T Max();
private:
T x, y;
};
template<typename T>
Compare<T>::Compare(T a, T b)
{
x = a;
y = b;
}
template<typename T>
T Compare<T>::Max()
{
return (x > y) ? x : y;
}
int main()
{
Compare<int> com1(3, 7);
Compare<double> com2(12.34, 56.78);
Compare<char> com3('a', 'x');
cout << com1.Max() << endl;
cout << com2.Max() << endl;
cout << com3.Max() << endl;
return 0;
}
6.3.3 有两个类型参数的类模板举例
#include <iostream>
using namespace std;
template<typename T1, typename T2>
class Myclass
{
public:
Myclass(T1 a, T2 b)
{
i = a;
j = b;
}
void show()
{
cout << "i = " << i << " j = " << j << endl;
}
private:
T1 i;
T2 j;
};
int main()
{
Myclass<int, double> ob1(12, 0.15);
Myclass<char, char *> ob2('x', "This is a test.");
ob1.show();
ob2.show();
return 0;
}
6.4 异常处理
6.4.1 传统的异常处理办法
程序中常见错误分为:编译时错误和运行时错误,编译时错误能够通过编译系统提醒进行修正。而运行时错误则不然,其中有写甚至不可预料,如算法出错;有些虽然可以预料却无法避免,如内存空间不足,无法实现指定的操作等;还有在函数调用时存在的一些错误,如无法打开输入文件、数组下标越界等。如果在程序中没有对这些错误的防范措施,往往得不到正确的运行结果甚至导致程序不正常中止,或出现死机现象。这类错误比较隐蔽,不容易被发现,是程序调试中的一个难点。程序在运行过程中出现的错误统称为异常,对异常的处理称为异常处理。我们在设计程序时,应当实现分析程序运行时可能出现的各种意外情况,并且分别制定出相应的处理方法,使程序能够继续执行,或者至少给出适当的提示信息。传统的异常处理方法基本上是采取判断或分支语句来实现。
#include <iostream>
using namespace std;
int Div(int x, int y);
int main()
{
cout << "7 / 3 = " << Div(7, 3) << endl;
cout << "5 / 0 = " << Div(5, 0) << endl;
return 0;
}
int Div(int x, int y)
{
if (y == 0)
{
cout << "error: the second number is zero!" << endl;
exit(0);
}
return x / y;
}
//传统的异常处理方法可以满足小型的应用程序的需要,但是在一个大型软件系统中,包含许多模块,每个模块又包含许多函数,
//函数之间又相互调用,比较复杂。如果在每一个函数中都设置处理异常的程序段,会使程序过于复杂和庞大。传统的异常处理机制无法
//保证程序的可靠运行,而且采用判断或者分支语句处理异常的方法不适合大量异常的处理,更不能处理不可预知的异常。C++提供的
//异常处理机制逻辑结构非常清晰,而且在一定程度上可以保证程序的健壮性。
6.4.2 异常处理方法
1. 概述
C++处理异常的办法是:如果在执行一个函数过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级(即调用函数)来解决,如果上一级函数也不能处理,就再传给其上一级,由其上一级(即调用函数)来解决,如果上一级函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上传,如果传到最高一级还无法处理,运行系统一般会自动调用系统函数terminate,由它调用abort中止程序。这样的异常处理方式使得异常的引发和处理机制分离,而不是由同一个函数完成。这样做法的好处是使底层函数(被调用函数)着重用于解决实际任务,而不必过多地考虑对异常的处理,以减轻底层函数的负担,而把处理异常的任务上移到上层去处理。例如在主函数中调用十几个函数,只需在主函数中设计针对不同类型的异常处理,而不必在每个函数中都设置异常处理,这样可以大大提高效率。
C++处理异常的机制是由检查、抛出和捕获3个部分组成的,分别由3种语句来完成:try(检查)、throw(抛出)和catch(捕获)。
2. 检查try、抛出throw和捕获catch
抛出
抛出异常使用throw语句,格式为
throw 表达式;
throw语句中的“表达式”是表示抛出的异常类型,异常类型由表达式的类型来表示,如
int Div(int x, int y)
{
if (y == 0)
throw y;//抛出异常,当除数y为0时,语句throw将抛出int型异常
return x / y;//当除数y不为0时,返回x/y的值
}
检查和捕获
异常的检查和捕获使用try语句和catch语句,如
thy
{
被检查的复合语句
}
catch (异常类型声明1)
{
进行异常处理的复合语句1
}
catch (异常类型声明2)
{
进行异常处理的复合语句2
}
.
.
.
catch (异常类型声明n)
{
进行异常处理的复合语句n
}
try之后的复合语句是被检查语句,也是容易引起异常的语句,这些语句称为代码的保护段。如果预料到某段程序代码(或对某个函数的调用)有可能发生异常就将它放在try之后。如果这段代码(或被调用函数)运行时真的遇到异常情况,其中的throw表达式就会抛出这个异常。
catch用来捕获throw抛出的异常,catch子句后面的复合语句是异常处理程序,异常类型声明部分指明了catch子句处理的异常的类型。catch在捕获到异常后由子句检查异常的类型,即检查throw后表达式的数据类型与哪个catch子句的异常类型的声明一致,如一致则执行相应的异常处理程序(该子句后的复合语句)。
3. 异常处理方法改写传统方法
#include <iostream>
using namespace std;
int Div(int x, int y);
int main()
{
try//检查异常
{ //被检查的复合语句
cout << Div(7, 3) << endl;
cout << Div(5, 0) << endl;
}
catch (int)//捕获异常,异常类型如果是int型
{ //进行异常处理的复合语句
cout << "The second number is zero, error!" << endl;
}
cout << "end" << endl;
return 0;
}
int Div(int x, int y)
{
if (y == 0)
throw y;//抛出异常,当除数y为0时,语句throw将抛出int型异常,抛出后返回上一级函数,抛出后面的语句将不会执行
cout << x << " / " << y << " = ";
return x / y;
}
4. 有多个catch块的异常处理程序
#include <iostream>
using namespace std;
//被检测的语句或程序段必须放在try之后,否则不起作用
//try和catch语句中必须有用花括号括起来的复合语句,即使花括号内只有一个语句也不能省略花括号。
//一个try_catch结构中只能有一个try块,但却可以有多个catch块,以便与不同的异常信息匹配。catch后面的括号中一般只写异常信息的类型名
int main()
{
double a = 2.5;
try
{
throw a;
}
catch (int)
{
cout << "error! Int!" << endl;
}
catch (double)
{
cout << "error! Double!" << endl;
}
cout << "end" << endl;
return 0;
}
//如果catch子句后括号中没有指定异常类型的信息,而是使用了三点删节号“···”,则表示它可以捕获任何类型的异常信息
5. 有删节号“...”的异常处理程序
#include <iostream>
using namespace std;
void func(int x)
{
if (x)
throw x;
}
int main()
{
try
{
func(5);
cout << "No here!" << endl;
}
catch (...)//这里是三个英文句号
{
cout << "Any type of error!" << endl;
}
cout << "end" << endl;
return 0;
}
//在某些情况下,在throw语句中可以不包括表达式,如
//throw;
//此时它表示把当前正在处理的异常信息再次抛出,给其上一层的catch处理
//C++中,一旦抛出一个异常,而程序又不捕获的话,那么系统就会调用一个系统函数terminate,由它调用abort终止程序
第8章 STL标准模板库
作为补充,详情可见“算法笔记”中“C++ STL”。
- 容器、迭代器和算法是STL的三个基本组成部分。容器包括vector、list、stack、queue、deque、set和map等,STL容器是对象的集合。STL算法是对容器进行处理的函数。迭代器就像指向容器中对象的指针,STL算法通过迭代器在容器中进行操作。迭代器实际是面向对象版本的指针。
2.vector容器中的at更加安全,不会访问vector内越界的元素
3.容器适配器是C++提供的3种模板类,与容器相结合,提供栈、队列和优先队列的功能。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】