C++简单总结

                       #第  1  章 C++的初步认识

1.1 从C到C++
C++保留了C语言原有的所有优点,并增加了面向对象的机制。C++对C的改进主要体现在增加了适用于面向对象程序设计的“ 类(class) ”
1.2 最简单的C++程序

#include<iostream>
using namespace std;
int main()
{
	cout<<"This is C++ program.\n";
	return 0;
}

cout 实际上是C++系统定义的对象名,称为输出流对象,“ << ”是“ 插入运算符 ”,与cout配合使用。cout语句中的endl是C++输出时的控制符,作用是换行(endl是end line的缩写,表示本行结束,与“ \n ”作用相同)
cin是C++系统定义的输入流对象,“ >> ”是“ 提取运算符 ”,与cin配合使用。

#include<iostream>
using namespace std;
int main()
{
	int max(int,int);
	int a,b,sum;
	cin>>a>>b;
	sum = max(a,b);
	cout<<"max="<<sum<<endl;
	return 0;
}
int max(int x,int y)
{
	int z;
	if(x>y)
		z=x;
	else
		z=y;
	return z;
}

上例中输入的两个数据间用一个或多个空格间隔,不能以逗号或其他符号间隔。

#include<iostream>
using namespace std;
class Student
{
private:
	int num;
	int score;
public:
	void setdata()
	{
		cin>>num;
		cin>>score;
	}
	void display()
	{
		cout<<"num="<<num<<endl;
		cout<<"score="<<score<<endl;
	}
};
Student stud1,stud2;
int main()
{
	stud1.setdata();
	stud2.setdata();
	stud1.display();
	stud2.display();
	return 0;
}

在c语言的结构体中只能包含数据成员,而在C++的类中可以包含两种成员,即数据和函数,分别称为数据成员和成员函数。
一个类是由一批数据以及对其操作的函数组成。类可以体现数据的封装性信息隐蔽
注意
<1>在大多数情况下,都把所有数据指定为私有,以实现信息隐蔽。
<2>凡是指定为公用的数据和函数,既可以被本类中的成员函数调用,也可以被类外的语句所调用。
<3>被指定为私有的成员(函数或数据)只能被本类中的成员函数所调用,而不能被类以外调用(以后介绍的“ 友元类除外 ”)
<4>具有“ 类 ”类型特征的变量称为“ 对象 ”,对象是占存储空间的,而类型并不占类型空间
<5>".“是一个“ 成员运算符 ”,把对象和成员连接起来。
1.3.1 C++的输入输出
1,用cout进行输出
<1>cout必须和输出运算符“ << ”一起使用。
<2>可以在一个输出语句中使用多个运算符“ << ”将多个输出项插入到输出流cout中,“ << ”运算符的结合方向为自左向右,因此各输出项按自左向右顺序插入到输出流中。
<3>用cout和” << “可以输出各种类型的数据。
<4>如果要指定输出所占的列数,可以用控制符setw进行设置,如setw(5)的作用是为其后面一个输出项预留5列的空间,如果输出数据项的长度不足5列,则数据向右对齐,若超过5列则按实际长度输出。(若使用setw,应当在程序的开头包含头文件iomanip或(iomanip.h))
2,用cin进行输入
<1>cin要与” >> "配合使用。
<2>程序中变量的定义放在了执行语句之后。在C语言程序中是不允许这样的,它要求声明部分必须在执行语句之前。而C++允许对变量的声明放在程序的任何位置(但必须在使用该变量之前)。
1.3.2 用 const 定义常变量
<1>在C语言中常用 #define 指令来定义符号常量,实际上,只是在预编译时进行字符置换。
<2>在C++中提供了用const定义常变量的方法,它具有变量的属性,有数据类型,占用存存储单元,有地址,可以用指针指向它,只是在程序运行期间变量的值是固定的,不能改变。一般把程序中不允许改变的变量定义为常变量。
1.3.3 函数原型声明
<1>在C语言程序中,如果函数调用的位置在函数定义之前,则应在函数调用之前对所调用的函数作声明,但如果所调用的函数是整型的,也可以不进行函数声明。
<2>在C++中,如果函数调用的位置在函数定义之前,则要求在函数调用之前必须对所调用的函数作函数原型声明,是强制性的。
一般形式为:
函数类型 函数名(参数表);
注意:参数表中一般包括参数类型和参数名,也可以只包括参数类型而不包括参数名,因为在编译时只检查参数类型,而不检查参数名。
13.4 函数的重载
<1>C语言规定在同一作用域(例如同一文件模块中)中不能有同名的函数,因此3个函数的名字不相同。
<2>C++允许在同一作用域中用同一函数名定义多个函数,这些函数的参数个数和参数类型不相同,这些同名的函数用来实现不同的功能。这就是函数的重载即一个函数名多用
注意:重载函数的参数个数或类型必须至少有其中之一不同,函数返回值类型可以相同也可以不同,但不允许参数个数和类型都相同而只有返回值类型不同。

#include<iostream>
using namespace std;
int max(int a,int b,int c)
{
	if(b>a)
		a=b;
	if(c>a)
		a=c;
	return a;
}
int max(int a,int b)
{
	if(b>a)
		a=b;
	return a;
}
int main()
{
	int a=10,b=2,c=-5;
	cout<<"max="<<max(a,b,c)<<endl;
	cout<<"max="<<max(b,c)<<endl;
	return 0;
}

1.3.5 函数模板
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只须在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
定义函数模板的一般形式为:
template < typename T> 或 template< class T>
注意
它只适用于函数的参数个数相同而类型不同,且函数体相同的情况。
1.3.6 有默认参数的函数
指定默认值的参数必须放在形参表列中的最右端,在调用有默认参数的函数时,实参的个数可以与形参不同,实参未给定的,从形参的默认值得到值。
注意
<1>如果函数的定义在函数的调用之前,则应在函数定义中给出默认值。如果函数的定义在函数调用之后,则在函数调用之前需要有函数声明,此时必须在函数声明中给出默认值,在函数定义时可以不给出默认值。
<2>由于函数声明在函数定义之前,因此以声明时给出的默认值为准,而忽略定义函数时指定的默认值。
<3>一个函数不能既作为重载函数,又作为有默认参数的函数。
1.3.7 变量的引用
1,引用的概念
在C++中变量的" 引用 “就是变量的别名,因此引用又称为别名
例如:int a;int &b = a;
注意
在上述声明中,&是” 引用声明符 ",此时它并不代表地址。不要理解为“ 把a的值赋给b的地址 ”。对变量声明一个引用,并不另开辟内存单元,b和a都代表同一变量单元。在声明一个引用时,必须同时使之初始化,即声明它代表哪一个变量。
2,引用的简单使用

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int &b = a;
	a = a*a;
	cout<<a<<" "<<b<<endl;
	b = b/5;
	cout<<b<<" "<<a<<endl;
	return 0;
}

3,关于引用的简单说明
<1>引用并不是一种独立的数据类型,它必须与某一种类型的数据相联系。声明引用时必须指定它代表的是哪个变量,即对它初始化。
<2>引用与其代表的变量共享同一内存单元,系统并不为引用另外分配存储空间。实际上,编译系统使引用和其代表的变量具有相同的地址。
<3>当&a的前面有类型符时,它必然是对引用的声明;如果前面没有类型符,此时的&是取地址运算符。
<4>对引用的初始化,可以用一个变量名,也可以用另一个引用。
<5>引用在初始化后不能再被重新声明为另一变量的别名。
4,将引用作为函数参数
<1>将变量名作为实参。
<2>传递变量的指针。
<3>传送变量的别名。
传值方式调用:将实参的值传送给形参,形参是实参的一个拷贝;
引用方式调用:将实参的地址传给引用型形参,这时形参与实参是同一个变量。
5,对引用的进一步说明
<1>不能建立void类型的引用。
<2>不能建立引用的数组。
<3>可以将变量的引用的地址赋给一个指针,此时指针指向的是原来的变量。
<4>可以建立指针变量的引用。
<5>可以用const对引用加以限定,不允许改变该引用的值。但是它并不阻止改变引用所代表的变量的值。
<6>可以用常量或表达式对引用进行初始化,但此时必须用const作声明。(编译系统是这样处理的:生成一个临时变量,用来存放该表达式的值,引用该临时变量的别名)用这种办法不仅可以用表达式对引用进行初始化,还可以用不同类型的变量对之初始化(要求能赋值兼容的类型)。
1.3.8 内置函数
嵌入到主调函数中的函数称为内置函数,又称内嵌函数。指定内置函数的方法很简单,只须在函数首行的左端加一个关键字inline即可。使用内置函数可以节省运行时间,但却增加了目标程序的长度。
1.3.9 作用域运算符
<1>根据规定,在main函数中的局部变量将屏蔽全局变量。
<2>C++提供作用域运算符“ :: ”,它能指定所需要的作用域,例如:::a表示全局作用域中的变量a。
1.3.10 字符串变量
C++标准库中声明的一个字符串类,用这种类可以定义对象。
1,定义字符串变量
字符串变量必须先定义后使用,定义字符串变量要用类名string。应当注意:要使用string类的功能时,必须在本文件的开头将C++标准库中的“ string ”头文件包含进来。
2,对字符串变量的赋值
<1>在定义了字符串变量后,可以用赋值语句对它赋以一个字符串常量,既可以用字符串常量给字符串变量赋值,也可以用一个字符串变量给另一个字符串变量赋值。
<2>在定义字符串变量时不需指定长度,它的长度随其中的字符串长度而改变。
<3>前面已说明,字符串常量以“ \0 ”作为结束符,但将字符串常量存放到字符串变量中时,只存放字符串本身而不包括“ \0 ”。
3,字符串变量的输入输出
可以在输入输出语句中用字符串变量名,输入输出字符串。
4,字符串变量的运算
<1>字符串的赋值用赋值号; 例如:string1 = string2;
<2>字符串连接用加号
<3>字符串比较用关系运算符
5,字符串数组
<1>在一个字符串数组中包含若干个元素,每个元素相当于一个字符串变量。
<2>每一个元素的长度是变化的,当向某一个一个元素重新赋值,其长度就可能发生变化。
<3>每一个字符串元素中只包含字符串本身的字符而不包括" \0 "。
<4>字符串变量中存放的是字符串的指针。
1.3.11 动态分配 / 撤销内存的运算符 new 和 delete
new运算符使用的一般格式为:
new 类型 [初值];
delete 运算符使用的一般格式为:
delete [ ]指针变量
注意:new 和 delete是运算符,不是函数,因此执行效率高。new要和delete配合使用。
第 1 章课后习题详解链接待补充

                       #**第  2  章  类和对象的特性**

2.1.1 什么是面向对象的程序设计
1,对象
客观世界中任何一个事物都可以看成一个对象。
<1>任何一个对象都应当具有属性和行为这两个要素。对象应能根据外界给的消息进行相应的操作。一个对象一般是由一组属性和一组行为构成。
<2>在C++中,每一个对象都是由数据和函数这两部分组成的,数据体现了前面提到的“ 属性 ”,函数时用来对数据进行操作的,以便实现某些功能。
2,封装与信息隐蔽
<1>可以对一个对象进行封装处理,把它的一部分属性和功能对外界屏蔽,也就是说从外面是看不到的,甚至是不可知的。
<2>把对象的内部实现和外部行为分隔开来。
<3>面向对象程序设计方法的一个重要特点就是“ 封装性 ”。所谓封装性:一是将有关的数据和操作代码封装在一个对象中,形成一个基本单位,各个对象之间相对独立,互不干扰。二是将对象中某些部分对外隐蔽,即隐蔽其内部细节,只留下少数接口,以便与外界联系,接受外界消息。
<4>C++z中的函数名就是对象的对外接口。
3,抽象
抽象的作用是表示同一类事物的本质。类是对象的抽象,而对象是类的特列,或者说是类的具体表现形式。
4,继承与重用
5,多态性
所谓多态性是指:由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。
2.1.3 类和对象的作用
<1>类是所有面向对象的语言的共同特征。
<2>一组数据是与一组操作相对应。因此人们设想把相关的数据和操作放在一起,形成一个整体,与外界相对分隔。这就是面向对象的程序设计中的对象。
<3>消息的作用就是对对象发出的命令,程序设计的关键是设计好每一个对象。以及确定向这些对象发出的命令,使各对象完成相应的操作。
2.1.4 面向对象的软件开发
<1>面向对象的分析。
<2>面向对象设计
<3>面向对象编程
<4>面向对象测试
<5>面向对象维护
2.2 类的声明和对象的定义
2.2.1 类和对象的关系
在C++中对象的类型称为类。类是抽象的,不占内存,而对象是具体的,占用存储空间。
2.2.2 声明类的类型
注意:如果在类的定义中既不指定private,也不指定public,则系统就默认为是私有的。
一般形式为:
class 类名
{
private:
私有的数据和成员函数;
public:
公用的数据和成员函数;
};
2.2.3 定义对象的方法
1,先声明类类型,然后再定义对象
<1>class 类名 对象名
<2>类名 对象名
2,在声明类的同时定义对象
3,不出现类名,直接定义对象
2.2.4 类和结构体的异同
用struct声明的类,如果对其成员不作private或public的声明,系统将其默认定为public(公用的)。
用class定义的类,如果不作private或public声明,系统将其成员默认定为private(私有的)。
2.3 类的成员函数
2.3.1 成员函数的性质
一般的做法是将需要被外界调用的成员函数指定为public,它们是类的对外接口。
2.3.2 在类外定义成员函数
类函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前,否则编译时会出错。
2.3.3 内置成员函数(inline成员函数)
为了减少时间开销,如果在类体中定义的成员函数中不包括循环等控制结构,C++系统会自动地对它们作为内置函数来处理。
应当注意的是:如果成员函数不在类体内定义,而在类体外定义,系统并不把它默认为内置函数。
2.3.4 成员函数的存储方式
<1>同一类的不同对象中的数据成员的值一般是不相同的,而不同对象的函数的代码是相同的,不论调用哪一个对象的函数的代码,其实调用的都是同样的内容。
<2>一个对象所占的空间大小只取决于该对象中的数据成员所占的空间,而与成员函数无关
<3>不同对象使用的是同一函数代码段,它怎么能够分别对不同的对象中的数据进行操作呢?C++为此专门设立了一个名为this的指针,用来指向不同的对象。
<4>不论成员函数在类内定义还是在类外定义,成员函数的代码段的存储方式是相同的,都不占用对象的存储空间
<5>不要将成员函数的这种存储方式和inline(内置)函数的概念混淆。无论是否用inline声明,成员函数的代码段都不占用对象的存储空间。inline函数值影响程序的执行效率,而与成员函数是否占用对象的存储空间无关。
<6>虽然成员函数并没有放在对象的存储空间中,但从逻辑的角度,成员函数是和数据一起封装在一个对象中的,只允许本对象中成员的函数访问同一对象中的私有数据。
2.4 对象的访问
<1>通过对象名和成员运算符访问对象中的成员
<2>通过指向对象的指针访问对象中的成员
<3>通过对象的引用访问对象中的成员
2.4.1 通过对象名和成员运算符访问对象中的成员
访问对象中成员的一般形式为:
对象名.成员名
<1>不仅可以在类外引用对象的公用数据成员,而且还可以调用对象的公用成员函数。
<2>在一个类中应当至少有一个公用的成员函数,作为对外的接口,否则就无法对对象进行任何操作。
2.4.2 通过指向对象的指针访问对象中的成员
在p指向t的前提下,p->hour,(*p).hour,t.hour三者等价
2.4.3 通过对象的引用来访问对象中的成员
引用与其代表的变量共享同一存储空间,因此完全可以用引用来访问对象中的成员。
2.5 类和对象的简单举例

#include<iostream>
using namespace std;
class Time
{
public:
	int hour;
	int minute;
	int sec;
};
int main()
{
	Time t1;
	cin>>t1.hour;
	cin>>t1.minute;
	cin>>t1.sec;
	cout<<t1.hour<<":"<<t1.minute<<":"<<t1.sec<<endl;
	return 0;
}

注意
<1>编译系统以函数声明时指定的默认参数值为准,在定义函数时指定的默认参数值不起作用
<2>在成员函数引用本对象的数据成员时,只须直接写数据成员名,因为在执行时,会根据this指针的指向,输出当前对象中的数据成员的值。
<3>定义成员函数时应该指定类名,因为定义的是该类中的成员函数,而调用成员函数时应该指定具体的对象名。

#include<iostream>
using namespace std;
class Array_max
{
private:
	int array[10];
	int max;
public:
	void set_value();
	void max_value();
	void show_value();
};
void Array_max::set_value()
{
	int i;
	for(i=0;i<10;i++)
		cin>>array[i];
}
void Array_max::max_value()
{
	int i;
	max = array[0];
	for(i=1;i<10;i++)
		if(array[i]>max)
			max = array[i];
}
void Array_max::show_value()
{
	cout<<"max="<<max;
}
int main()
{
	Array_max arrmax;
	arrmax.set_value();
	arrmax.max_value();
	arrmax.show_value();
	return 0;
}

2.6 类的封装性和信息隐蔽
2.6.1 公用接口与私有实现的分离
<1>类的作用是把数据和算法封装在用户声明的抽象数据类型中。
<2>在面向对象的程序设计中,在声明类时,一般都是把所有的数据指定为私有的,使它们与外界隔离。把需要外界调用的成员函数来引用甚至修改私有数据成员。
<3>通过成员函数对数据成员进行操作称为类的功能的实现。
<4>当接口与实现(对数据的操作)分离时,只要类的接口没有改变,对私有实现的修改不会引起程序的其他部分的修改
2.6.2 类声明和成员函数定义的分离
<1>类声明头文件是用户使用类库的公用接口。
<2>在系统提供的头文件中只包括对成员函数的声明,而不包括成员函数的定义。类声明和函数定义是分别放在两个文件中。
<3>一个C++程序是由3个部分组成:<1>类声明头文件;<2>类实现文件,包括类成员函数的定义;<3>类的使用文件,即主文件。
第 2 章课后习题详解链接待补充

                       #**第  3  章  怎样使用类和对象**

3.1 利用构造函数对类对象的初始化
3.1.1 对象的初始化
<1>如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化。但是,如果数据成员是私有的,或者类中有 private 或 protected的数据成员,就不能用这种方法初始化。
3.1.2 用构造函数实现数据的初始化
C++提供了构造函数来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是建立对象时,自动执行。
注意:构造函数的名字必须与类名同名,而不能任意命名,以便编译系统能识别它,并把它作为构造函数处理。它不具有任何类型,不返回任何值。
<1>可以只在类内对构造函数进行声明而在类外定义构造函数。
<2>在建立类对象时会自动调用构造函数。
<3>构造函数没有返回值,它的作用只是对对象进行初始化。因此也不需要在定义构造函数时声明类型。
<4>构造函数不需要用户调用,也不能被用户调用。构造函数是在定义对象时由系统自动执行,而且只能执行一次。构造函数一般声明为public。
<5>可以用一个类对象初始化另一个类对象。
<6>如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,只是这个构造函数函数体是空的,也没有参数,不执行初始化操作。
3.1.3 带参数的构造函数
用户希望对不同的对象赋予不同的初值,这时就无法使用上面的方法,可以采用带参数的构造函数。
构造函数首部的一般格式为:
构造函数名(类型1 形参1,类型2 形参2,…)
前面已说明:用户是不能调用构造函数的,因此无法采用常规的调用函数的方法给出实参。实参是在定义对象时给出的。定义对象的一般格式为
类名 对象名(实参1,实参2,…)
在建立对象时把实参的值传递给构造函数相应的形参,把它们作为数据成员的初值。

//有2个长方体,求它们的体积
#include<iostream>
using namespace std;
class Box
{
private:
	int height;
	int width;
	int length;
public:
	Box(int,int,int);
	int volume();
};
Box::Box(int h,int w,int len)
{
	height = h;
	width = w;
	length = len;
}
int Box::volume()
{
	return (height*width*length);
}
int main()
{
	Box box1(12,25,30);
	cout<<"The volume of box1 is "<<box1.volume()<<endl;
	Box box2(15,30,21);
	cout<<"The volume of box2 is "<<box2.volume()<<endl;
	return 0;
}

3.1.4 用参数初始化表对数据成员初始化
带有参数初始化表的构造函数的一般形式如下:
类名::构造函数([ 参数表 ])[ :成员初始化表 ](其中方括号为可选项)
{
[ 构造函数体 ]
}
注意:如果数据成员是数组,则应当在构造函数的函数体中用语句对其赋值,而不能在参数初始化表中对其初始化。
3.1.5 构造函数的重载
这些构造函数具有相同的名字,而参数的个数或参数的类型不相同,这称为构造函数的重载
怎么辨别调用哪一个构造函数?是根据函数调用的形式去确定对应哪一个构造函数
<1>在建立对象时不必给出实参的构造函数,称为默认构造函数。显然,无参构造函数属于默认构造函数。一个类只能有一个默认构造函数。如果用户未定义构造函数,则系统会自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。
<2>尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。
3.1.6 使用默认参数的构造函数
构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参,编译系统就使形参的值为默认值。

#include<iostream>
using namespace std;
class Box
{
private:
	int height;
	int width;
	int length;
public:
	Box(int h=10,int w=10,int len =10);
	int volume();
};
Box::Box(int h,int w,int len)
{
	height = h;
	width = w;
	length = len;
}
int Box::volume()
{
	return (height*width*length);
}
int main()
{
	Box box1;
	cout<<"The volume of box1 is "<<box1.volume()<<endl;
	Box box2(15);
	cout<<"The volume of box2 is "<<box2.volume()<<endl;
	Box box3(15,30);
	cout<<"The volume of box3 is "<<box3.volume()<<endl;
	Box box4(15,30,20);
	cout<<"The volume of box4 is "<<box4.volume()<<endl;
	return 0;
}

<1>在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。
<2>如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。
<3>一个类只能有一个默认构造函数,也就是说,可以不使用参数而调用的构造函数,一个类只能有一个。
<4>在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。
3.2 利用析构函数进行清理工作
<1>如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
<2>静态局部对象在函数调用结束时对象并不释放,因此也不调用析构函数。只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
<3>如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
<4>析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
<5>析构函数不返回任何值,没有函数类型,也没有函数参数,因此它不能被重载。
3.3 调用构造函数和析构函数的顺序
先构造的后析构,后构造的先析构。
3.4 对象数组
对象数组的每一个元素都是同类的对象。
实现初始化:
<1>在花括号中分别写出构造函数名并在括号内指定实参。

#include<iostream>
using namespace std;
class Box
{
private:
	int height;
	int width;
	int length;
public:
	Box(int h=10,int w=12,int len=15):height(h),width(w),length(len){}
	int volume();
};
int Box::volume()
{
	return (height*width*length);
}
int main()
{
	Box a[3] = {
		Box(10,12,15),
		Box(15,18,20),
		Box(16,20,26)
	};
	cout<<"volume of a[0] is "<<a[0].volume()<<endl;
	cout<<"volume of a[1] is "<<a[1].volume()<<endl;
	cout<<"volume of a[2] is "<<a[2].volume()<<endl;
	return 0;
}

3.5 对象指针
3.5.1 指向对象的指针
一个对象存储空间的起始地址就是对象的指针。可以定义一个指针变量,用来存放对象的地址,这就是指向对象的指针变量。
定义指向类对象的指针变量的一般形式为:
类名 * 对象指针名;
5.2 指向对象成员的指针
1,指向对象数据成员的指针
数据类型名 * 指针变量名;
2,指向对象成员函数的指针
定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。
普通函数:类型名(*指针变量名)(参数表列)
公用成员函数:数据类型名(类名:: * 指针变量名 )(参数表列);
使指针变量指向一个公用成员函数的一般形式为
指针变量名 = &类名::成员函数名;
3.5.3 指向当前对象的 this 指针
<1>在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。它是指向本类对象的指针,它的值是当前被调用函数的成员函数所在的对象的起始地址。
<2>this指针是隐式使用的,它是作为参数传递给成员函数的。
3.6.1 常对象
定义常对象的一般形式为:(常对象必须要有初值)
类名 const 对象名[ (实参表) ];或者 const 类名 对象名 [ 实参表 ]
<1>如果一个对象呗声明为常对象,则通过该对象只能调用它的常成员函数,而不能调用该对象的普通成员函数(除了由系统自动调用的隐式的构造函数和析构函数)。常成员函数是常对象唯一的对外接口。这是为了防止普通成员函数会修改常对象函数的值。
<2>不能调用常对象中的普通成员函数和常成员函数不能修改对象的数据成员。
<3>如果一定要修改常对象中的某个数据成员的值,对该数据成员声明为mutable,这样就可以用声明为const的成员函数来修改它的值。
3.6.2 常对象成员
1,常数据成员
<1>只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值。
<2>构造函数只能用参数初始化表对常数据成员初始化。
2,常成员函数
<1>如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们。
<2>一般格式为:类型名 函数名(参数表)const
<3>常成员函数可以引用const数据成员,也可以引用非const的数据成员。const数据成员可以被const成员函数引用,也可以被非const的成员函数引用。
3.6.3 指向对象的常指针
一般形式为:
类名 * const 指针变量名;
3.6.4 指向常对象的指针变量
一般形式为:
const 类型名 * 指针变量名
<1>如果一个对象已被声明为常变量,只能用指向常变量的指针变量指向它
<2>指向常变量的指针变量可以指向一个非const变量。这时可以通过指针变量访问该变量,但不能改变该变量的值。。
<3>用指针变量访问c1期间,c1具有常变量的特征。
<4>如果一个对象已被声明为常对象,只能通过指向常对象的指针变量指向它。
<5>如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过该指针变量来改变的。
注意
当希望在调用函数时对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同时用对象的地址作实参(对象可以是const或非const型)
3.6.5 对象的常引用
一个变量的引用就是变量的别名。
3.7 对象的动态建立和释放
用new运算符动态地分配内存后,将返回一个指向新对象的指针,即所分配的内存空间的起始地址。
在不再需要使用有new建立的对象时,可以用delete运算符予以释放。
3.8 对象的赋值和复制
3.8.1 对象的赋值
一般形式为:
对象名1 = 对象名2;
对象的赋值只是对其中数据成员的赋值。
3.8.2 对象的复制
一般形式为:
类名 对象2(对象1);(用对象1复制出对象2)
C++还提供另一种方便用户的复制形式:
类名 对象名2 = 对象名1;
3.9 静态成员
3.9.1 静态数据成员
<1>静态的数据成员在内存中只占一份空间,每一个对象都可以引用这个静态数据成员。
<2>静态数据成员是在所有对象之外单独开辟空间,,只要在类中指定了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。
<3>在一个类中可以有一个或多个静态数据成员,所有对象都共享这些静态数据成员,都可以引用它。
<4>静态数据成员是在编译时被分配空间的,到程序结束时才释放空间。
<5>静态数据成员可以初始化,但只能在类体外进行初始化。
一般形式为:
数据类型 类名 ::静态数据成员名 = 初值;
<6>如果未对静态数据成员赋初值,则编译系统会自动赋予初值0。静态数据成员既可以通过对象名引用,也可以通过类名来引用。

//输出立方体的体积,使用静态数据成员
#include<iostream>
using namespace std;
class Box
{
public:
	Box(int,int);
	int volume();
	static int height;
	int width;
	int length;
};
Box::Box(int w,int len)
{
	width = w;
	length = len;
}
int Box::volume()
{
	return (height*width*length);
}
int Box::height = 10;
int main()
{
	Box a(15,20),b(20,30);
	cout<<a.height<<endl;
	cout<<b.height<<endl;
	cout<<Box::height<<endl;
	cout<<a.volume()<<endl;
	return 0;
}

注意:静态成员的作用域只限于定义该类的作用域内(如果是在一个函数中定义类,那么其中静态数据成员的作用域就是此函数内)
3.9.2 静态成员函数
静态成员函数是类的一部分而不是对象的一部分。如果要在类外调用公用的静态成员函数,要用类名和域运算符“::”
注意:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。静态成员函数可以直接引用本类中的静态成员。在C++程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态数据成员。但是,如果一定要引用本类的非静态成员,应该加对象名和成员运算符“ . ”。
公用的成员函数可以引用本对象中的一般数据成员(非静态数据成员),也可以引用类中的静态数据成员。
在C++程序中最好养成:只用静态成员函数引用静态数据成员,而不引用非静态数据成员。

//统计学生平均成绩。使用静态成员函数
#include<iostream>
using namespace std;
class Student
{
private:
	int num;
	int age;
	float score;
	static float sum;
	static int count;
public:
	Student(int n,int a,float s):num(n),age(a),score(s){}
	void total();
	static float average();
};
void Student::total()
{
	sum+=score;
	count++;
}
float Student::average()
{
	return (sum/count);
}
float Student::sum=0;
int Student::count=0;
int main()
{
	Student stud[3]={
		Student(1001,18,70),
		Student(1002,19,78),
		Student(1003,20,98)
	};
	int n;
	cout<<"please input the number of students:";
	cin>>n;
	for(int i=0;i<n;i++)
		stud[i].total();
	cout<<"the average score of "<<n<<" student is "<<Student::average()<<endl;
	return 0;
}

3.10 友元
3.10.1 友元函数
在类体中用friend对其进行声明,此函数就称为本类的友元函数。友元函数可以访问这个类的私有成员。

#include<iostream>
using namespace std;
class Time
{
private:
	int hour;
	int minute;
	int sec;
public:
	Time(int,int,int);
	friend void display(Time &);
};
Time::Time(int h,int m,int s)
{
	hour = h;
	minute = m;
	sec = s;
}
void display(Time &t)
{
	cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;
}
int main()
{
	Time t1(10,13,56);
	display(t1);
	return 0;
}

但应注意在引用这些私有成员时,必须加上对象名,因为display函数不是Time类的成员函数,没有this指针,不能默认引用Time类的数据成员,必须指定要访问的对象。
2,友元成员函数
friend函数不仅可以是一般函数(非成员函数),而且可以是另一个类中的成员函数。

#include<iostream>
using namespace std;
class Date;                  //对Date类的提前引用声明
class Time
{
private:
	int hour;
	int minute;
	int sec;
public:
	Time(int,int,int);
	void display(Date &);    //display是成员函数,形参是Date类对象的引用
};
class Date
{
private:
	int month;
	int day;
	int year;
public:
	Date(int,int,int);
	friend void Time::display(Date &);  //声明Time类中的display函数为本类的友元成员函数
};
Time::Time(int h,int m,int s)
{
	hour = h;
	minute = m;
	sec = s;
}
Date::Date(int m,int d,int y)
{
	month = m;
	day = d;
	year = y;
}
void Time::display(Date &d)
{
	cout<<d.month<<"/"<<d.day<<"/"<<d.year<<endl;
	cout<<hour<<":"<<minute<<":"<<sec<<endl;
}
int main()
{
	Time t1(10,13,56);
	Date d1(12,25,2004);
	t1.display(d1);
	return 0;
}

注意:在输出本类的时,分,秒时,不必使用对象名,而在输出Date类的对象中的年,月,日时,就必须加上对象名。
一个函数(包括普通函数和成员函数)可以被多个类声明为“朋友”,这样就可以引用多个类中的私有成员
3.10.2 友元类
声明友元类的一般形式为:
friend 类名;
注意
<1>友元关系是单向的而不是双向的。如果声明了类B是类A的友元类,不等于类A是类B的友元类,类A中的成员函数不能访问类B的私有数据。
<2>友元关系不能传递,如果类B是类A的友元类,类C是类B的友元类,不等于类C是类A的友元类。
3.11 类模板
声明类模板时要增加一行:
template < class 类型参数名>。这样就能实现“ 一类多用 ”。类模板是类的抽象,类是类模板的实例。
其一般形式为:(在主函数使用时的格式)
类模板名 <实际类型名> 对象名(参数表);
如果在类模板外定义成员函数,应写成类模板形式:
template < class 虚拟类型参数>
函数类型 类模板名 <虚拟类型参数>::成员函数名(函数形参表){…}。
第 3 章课后习题详解链接待补充

                       # 第  4  章  对运算符进行重载

4.1 为什么要对运算符重载

//通过函数来用“+”号实现复数相加(没有用运算符重载)
#include<iostream>
using namespace std;
class Complex
{
private:
	double real;
	double imag;
public:
	Complex(){real=0;imag=0;}
	Complex(double r,double i){real = r;imag = i;}
	Complex complex_add(Complex &c2);
	void display();
};
Complex Complex::complex_add(Complex &c2)
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}
void Complex::display()
{
	cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main()
{
	Complex c1(3,4),c2(5,-10),c3;
	c3 = c1.complex_add(c2);
	cout<<"c1=";c1.display();
	cout<<"c2=";c2.display();
	cout<<"c1+c2=";c3.display();
	return 0;
}

4.2 对运算符重载的方法
运算符重载的方法是定义一个重载运算符的函数,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载。
重载运算符的函数一般格式如下:
函数原型 operator 运算符名称(形参表)
{对运算符的重载处理}。函数“ operator+ ”重载了运算符“ + ”

//对运算符“+”实行重载,使之能用于两个复数相加
#include<iostream>
using namespace std;
class Complex
{
private:
	double real;
	double imag;
public:
	Complex(){real=0;imag=0;}
	Complex(double r,double i){real=r;imag=i;}
	Complex operator+(Complex &c2);
	void display();
};
Complex Complex::operator+(Complex &c2)
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}
void Complex::display()
{
	cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main()
{
	Complex c1(3,4),c2(5,-10),c3;
	c3 = c1 + c2;
	cout<<"c1=";c1.display();
	cout<<"c2=";c2.display();
	cout<<"c1+c2=";c3.display();
	return 0;
}

在将运算符“ + ”重载为类的成员函数后,C++编译系统将程序中的表达式c1+c2解释为:c1.operator+(c2)
4.3 重载运算符的规则
<1>C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。
<2>C++绝大部分的运算符允许重载。不能重载的运算符只有5个:. * :: sizeof ?:
前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。
<3>重载不能改变运算符运算对象(即操作数)的个数。
<4>重载不能改变运算符的优先级别。
<5>重载不能改变运算符的结合性。
<6>重载运算符的函数不能有默认的参数。
<7>重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。
<8>用于类对象的运算符一般必须重载,但有两个例外,运算符“ = ”和“ & ”不必用户重载。
<9>从理论来说,可以将一个运算符重载为执行任意操作。但应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。
4.4 运算符重载函数作为类成员函数和友元函数
对运算符重载的函数有两种处理方式:
<1>把运算符重载的函数作为类的成员函数;
<2>运算符重载的函数不是类的成员函数(可以是一个普通函数),在类中把它声明为友元函数。

#include<iostream>
using namespace std;
class Complex
{
private:
	double real;
	double imag;
public:
	Complex(){real=0;imag=0;}
	Complex(double r,double i){real = r;imag = i;}
	friend Complex operator+(Complex &c1,Complex &c2);
	void display();
};
Complex operator+(Complex &c1,Complex &c2)
{
	return Complex(c1.real+c2.real,c1.imag+c2.imag);
}
void Complex::display()
{
	cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main()
{
	Complex c1(3,4),c2(5,-10),c3;
	c3 = c1 + c2;
	cout<<"c1=";c1.display();
	cout<<"c2=";c2.display();
	cout<<"c1+c2=";c3.display();
	return 0;
}

注意
<1>如果将运算符重载函数作为成员函数,它可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数的参数。
<2>将双目运算符重载为友元函数时,由于友元函数不是该类的成员函数,因此在函数的形参表列中必须有两个参数,不能省略。
<3>C++规定,赋值运算符=,下标运算符[ ],函数调用运算符(),成员运算符->必须作为成员函数。
<4>流插入<<和流提取运算符>>,类型转换运算符不能定义为类的成员函数,只能作为友元函数。
<5>一般将单目运算符和复合运算符(+=,-=,/=,*=,&=,!=,=,%=,>>=,<<=)重载为成员函数。
<6>一般将双目运算符重载为友元函数。
4.5 重载双目运算符

#include<iostream>
using namespace std;
class String
{
private:
	char *p;               //字符型指针,用于指向字符串
public:
	String(){p = NULL;}
	String(char * str);
	friend bool operator > (String &string1,String &string2);
	friend bool operator < (String &string1,String &string2);
	friend bool operator == (String &string1,String &string2);
	void display();
};
String::String(char * str)
{
	p = str;
}
void String::display()
{
	cout<<p;
}
bool operator > (String &string1,String &string2)
{
	if(strcmp(string1.p,string2.p) > 0)
		return true;
	else
		return false;
}
bool operator < (String &string1,String &string2)
{
	if(strcmp(string1.p,string2.p) < 0)
		return true;
	else
		return false;
}
bool operator == (String &string1,String &string2)
{
	if(strcmp(string1.p,string2.p) == 0)
		return true;
	else
		return false;
}
void compare(String &string1,String &string2)
{
	if(operator>(string1,string2)==1)
	{
		string1.display();cout<<">";string2.display();
	}
	else
		if(operator < (string1,string2) == 1)
		{
			string1.display();cout<<"<";string2.display();
		}
		else
			if(operator == (string1,string2) == 1)
			{string1.display();cout<<"=";string2.display();}
	cout<<endl;
}
int main()
{
	String string1("Hello"),string2("Book"),string3("Computer"),
	string4("Hello");
	compare(string1,string2);
	compare(string2,string3);
	compare(string1,string4);
	return 0;
}

4.6 重载单目运算符
由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数。
C++约定:在自增(自减)运算符重载函数中,增加一个int型形参,就是后置自增(自减)运算符函数。

#include<iostream>
using namespace std;
class Time
{
private:
	int minute;
	int sec;
public:
	Time(){minute = 0;sec = 0;}
	Time(int m,int s):minute(m),sec(s){}
	Time operator ++ ();        //声明前置自增运算符“++”重载函数
	Time operator ++ (int);     //声明后置自增运算符“++”重载函数
	void display(){cout<<minute<<":"<<sec<<endl;}
};
Time Time::operator ++()
{
	if(++sec>=60)
	{
		sec -= 60;
		++minute;
	}
	return *this;
}
Time Time::operator++(int)
{
	Time temp(*this);
	sec++;
	if(sec>=60)
	{
		sec-=60;
		++minute;
	}
	return temp;
}
int main()
{
	Time time1(34,59),time2;
	cout<<"time1:";
	time1.display();
	++time1;
	cout<<"++time1:";
	time1.display();
	time2 = time1++;
	cout<<"time1++:";
	time1.display();
	cout<<"time2:";
	time2.display();
	return 0;
}

4.7 重载流插入运算符和流提取运算符
对“ << ”和“ >> ”重载的函数形式如下:
istream & operator >> (istream &,自定义类 &);
ostream & operator << (ostream &,自定义类 &);
注意
只能将重载“ >> ”和“ << ”的函数作为友元函数,而不能将它们定义为成员函数。
4.7.1 重载流插入运算符“ << ”
运算符“ << ”重载函数的第一个参数和函数的类型都必须是ostream类型的引用,就是为了返回cout的当前值以便连续输出。
4.7.2 重载流提取运算符“ >> ”

#include<iostream>
using namespace std;
class Complex
{
private:
	double real;
	double imag;
public:
	friend ostream& operator << (ostream&,Complex&);  //声明友元重载运算符"<<"函数
	friend istream& operator >> (istream&,Complex&);  //声明友元重载运算符">>"函数
};
ostream& operator << (ostream &output,Complex &c)
{
	output<<"("<<c.real<<"+"<<c.imag<<"i)";
	return output;
}
istream& operator >> (istream &input,Complex &c)
{
	cout<<"input real part and imaginary part of complex number:";
	input>>c.real>>c.imag;
	return input;
}
int main()
{
	Complex c1,c2;
	cin>>c1>>c2;
	cout<<"c1="<<c1<<endl;
	cout<<"c2="<<c2<<endl;
	return 0;
}

4.9 不同类型数据间的转换
4.9.1 标准类型数据间的转换
由C++编译系统自动完成的,用户不需要干预。这种转换称为隐式类型转换
C++还提供显式类型转换,指定一种数据转换成另一指定的类型,其形式为:
类型名(数据)或(类型名)数据
4.9.2 用转换构造函数进行不同类型数据的转换
转换构造函数的作用是将一个其他类型的数据转换成一个类的对象。
转换构造函数只能有一个参数。如果有多个参数,就不是转换构造函数。
4.9.3 类型转换函数
类型转换函数的作用是将一个类的对象转换成另一类型的数据。
一般形式为:
operator 类型名()
{实现转换的语句}
<1>在函数名前面不能指定函数类型,函数没有参数。
<2>类型转换函数只能作为成员函数,因为转换的主体是本类的对象。不能作为友元函数或普通函数

#include<iostream>
using namespace std;
class Complex
{
private:
	double real;
	double imag;
public:
	Complex(){real=0;imag=0;}
	Complex(double r,double i){real = r;imag = i;}
	operator double(){return real;}
};
int main()
{
	Complex c1(3,4),c2(5,-10),c3;
	double d;
	d = 2.5 + c1;
	cout<<d<<endl;
	return 0;
}

在已定义了相应的转换构造函数情况下,将运算符“ + ”函数重载为友元函数,在进行两个复数相加时,可以用交换律。
结论
如果运算符函数重载为成员函数,它的第一个参数必须是本类的对象。当第一个操作数不是类对象时,不能将运算符函数重载为成员函数。如果将运算符+函数重载为类的成员函数,交换律不适用。由于这个原因,一般情况下将双目运算符函数重载为友元函数。单目运算符则多重载为成员函数。
第 4 章课后习题详解答案链接待补充

                       **#第  5  章  继承与派生**

面向对象程序设计的4个主要特点为:抽象,封装,继承和多态性
5.1 什么是继承和派生
<1>在C++中可重用性是通过“ 继承 ”这一机制来实现的。因此,继承是C++的一个重要组成部分。
<2>在C++中所谓“ 继承 ”就是在一个已存在的类的基础上建立一个新的类。已存在的类(例如“ 马 ”)称为“ 基类 ”或“ 父类 ”。新建立的类(例如“公马”)称为“ 派生类 ”或“ 子类 ”。
<3>一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。
<4>一个派生类只从一个基类派生,这称为单继承。一个派生类有两个或多个基类的称为多重继承。
<5>派生类是基类的具体化,而基类则是派生类的抽象。
5.2 派生类的声明方式
声明派生类的一般形式为
class 派生类名:[ 继承方式 ] 基类名
{
派生类新增加的成员
};
继承方式包括:public(公用的),private(私有的)和protected(受保护的),继承方式是可选的,如果不写此项,则默认为private(私有的)
5.3 派生类的构成
构造一个派生类包括以下3部分工作。
<1>从基类接受成员。派生类把基类全部的成员(不包括构造函数和析构函数)接受过来。
<2>调整从基类接受的成员。可以通过继承把基类的公用成员指定为在派生类中的访问属性为私有(派生类外不能访问)。还可以通过在派生类中声明一个与基类成员同名的成员,则派生类的新成员会覆盖基类的同名成员。但应注意:如果是成员函数,不仅应使函数名相同,而且函数的参数表(参数的个数和类型)也应相同。
<3>在声明派生类时增加的成员。派生类是抽象基类的具体实现。
5.4 派生类成员的访问属性
<1>公用继承
基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。
<2>私有继承
基类的公有成员和保护成员在派生类中成了私有成员。其私有成员仍为基类私有。
<3>受保护的继承
基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。
保护成员的意思是:不能被外界引用,但可以被派生类的成员引用
5.4.1 公用继承
在建立一个派生类时将基类的继承方式指定为public,称为公用继承,用公用继承的方式建立的派生类称为公用派生类,其基类称为公用基类。

#include<iostream>
#include<string>
using namespace std;
class Student
{
private:
	int num;
	string name;
	char sex;
public:
	void get_value()
	{cin>>num>>name>>sex;}
	void display()
	{
		cout<<"num: "<<num<<endl;
		cout<<"name: "<<name<<endl;
		cout<<"sex: "<<sex<<endl;
	}
};
class Student1:public Student
{
private:
	int age;
	string addr;
public:
	void get_value_1()
	{cin>>age>>addr;}
	void display_1()
	{
		cout<<"age: "<<age<<endl;
		cout<<"address: "<<addr<<endl;
	}
};
int main()
{
	Student1 stud;
	stud.get_value();
	stud.get_value_1();
	stud.display();
	stud.display_1();
	return 0;
}

5.4.2 私有继承
在声明一个派生类时将基类的继承方式指定为private的,称为私有继承,用私有继承方式建立的派生类称为私有派生类,其基类称为私有基类。

#include<iostream>
#include<string>
using namespace std;
class Student
{
private:
	int num;
	string name;
	char sex;
public:
	void get_value()
	{cin>>num>>name>>sex;}
	void display()
	{
		cout<<"num: "<<num<<endl;
		cout<<"name: "<<name<<endl;
		cout<<"sex: "<<sex<<endl;
	}
};
class Student1:private Student
{
private:
	int age;
	string addr;
public:
	void get_value_1()
	{get_value();
	cin>>age>>addr;}
	void display_1()
	{
		display();
		cout<<"age: "<<age<<endl;
		cout<<"address: "<<addr<<endl;
	}
};
int main()
{
	Student1 stud1;
	stud1.get_value_1();
	stud1.display_1();
	return 0;
}

5.4.3 保护成员和保护继承
由protected声明的成员称为“ 受保护的成员 ”,或简称“ 保护成员 ”。受保护成员不能被类外访问,保护成员可以被派生类的成员函数引用。
在定义一个派生类时将基类的继承方式指定为protected的,称为保护继承,用保护继承方式建立的派生类称为保护派生类,其基类称为受保护的基类,简称保护基类。
<1>保护基类的所有成员在派生类中都被保护起来,类外不能访问,其公用成员函数和保护成员可以被其派生类的成员函数访问,私有成员则不可访问。

#include<iostream>
#include<string>
using namespace std;
class Student
{
protected:
	int num;
	string name;
	char sex;
public:

};
class Student1 : protected Student
{
private:
	int age;
	string addr;
public:
	void get_value1();
	void display1();
};
void Student1::get_value1()
{
	cin>>num>>name>>sex;
	cin>>age>>addr;
}
void Student1::display1()
{
	cout<<"num: "<<num<<endl;
	cout<<"name: "<<name<<endl;
	cout<<"sex: "<<sex<<endl;
	cout<<"age: "<<age<<endl;
	cout<<"address: "<<addr<<endl;
}
int main()
{
	Student1 stud1;
	stud1.get_value1();
	stud1.display1();
	return 0; 
}

保护成员和私有成员的不同之处,在于把保护成员的访问范围扩展到派生类中。
5.4.4 多级派生时的访问属性
在多级派生的情况下,各成员的访问属性仍按以上原则确定。
5.5 派生类的构造函数和析构函数
基类的构造函数是不能继承的,在声明派生类时,派生类并没有把基类的构造函数继承过来,因此,对继承过来的基类成员初始化的工作也要由派生类的构造函数承担。希望在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。解决这个问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。
5.5.1 简单的派生类的构造函数
派生类构造函数一般形式为:
派生类构造函数名(总参数表):基类构造函数名(参数表)
{派生类中新增数据成员初始化语句}
5.5.2 有子对象的派生类的构造函数

#include<iostream>
#include<string>
using namespace std;
class Student
{
protected:               //保护部分
	int num;
	string name;
public:
	Student(int n,string nam)
	{
		num = n;
		name = nam;
	}
	void display()
	{
		cout<<"num: "<<num<<endl<<"name:"<<name<<endl;
	}
};
class Student1:public Student
{
private:
	Student monitor;
	int age;
	string addr;
public:
	Student1(int n,string nam,int n1,string nam1,   //派生类构造函数
		int a,string ad):Student(n,nam),monitor(n1,nam1)
	{
		age = a;
		addr = ad;
	}
	void show()
	{
		cout<<"This student is: "<<endl;
		display();         //输出num和name
		cout<<"age: "<<age<<endl;
		cout<<"address: "<<addr<<endl<<endl;
	}
	void show_monitor()
	{
		cout<<endl<<"Class monitor is : "<<endl;
		monitor.display();
	}
};
int main()
{
	Student1 stud1(10010,"Wang_li",10001,"Li_jun",19,
		"115 Beijing Road,Shanghai");
	stud1.show();
	stud1.show_monitor();
	return 0;
}

定义派生类构造函数的一般形式为:
派生类构造函数名(总参数表):基类构造函数名(参数表),子对象名(参数表)
{派生类中新增数据成员初始化}
5.5.3 多层派生时的构造函数

//多级派生情况下派生类的构造函数
#include<iostream>
#include<string>
using namespace std;
class Student
{
protected:
	int num;
	string name;
public:
	Student(int n,string nam)
	{
		num = n;
		name = nam;
	}
	void display()
	{
		cout<<"num: "<<num<<endl;
		cout<<"name: "<<name<<endl;
	}
};
class Student1:public Student
{
private:
	int age;
public:
	Student1(int n,string nam,int a):Student(n,nam)
	{
		age = a;
	}
	void show()
	{
		display();
		cout<<"age: "<<age<<endl;
	}
};
class Student2:public Student1
{
private:
	int score;
public:
	Student2(int n,string nam,int a,int s):Student1(n,nam,a)
	{score = s;}
	void show_all()
	{
		show();
		cout<<"score: "<<score<<endl;
	}
};
int main()
{
	Student2 stud(10010,"Li",17,89);
	stud.show_all();
	return 0;
}

注意
不要列出每一层派生类的构造函数,只须写出其上一层派生类(即它的直接基类)的构造函数。
5.5.4 派生类构造函数的特殊形式
如果在基类和子对象类型的声明中都没有定义带参数的构造函数,而且也不需要对派生类自己的数据成员初始化,则不必显式地定义派生类构造函数。
5.5.5 派生类的析构函数
先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。
5.6 多重继承
5.6.1 声明多重继承的方法
5.6.2 多重继承派生类的构造函数
派生类构造函数名(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),基类3构造函数(参数表列)
{派生类中新增数据成员初始化语句}
派生类构造函数的执行顺序同样为:先调用基类的构造函数,再执行派生类构造函数的函数体。

#include<iostream>
#include<string>
using namespace std;
class Teacher
{
protected:
	string name;
	int age;
	string title;
public:
	Teacher(string nam,int a,string t)
	{
		name = nam;
		age = a;
		title = t;
	}
	void display()
	{
		cout<<"name: "<<name<<endl;
		cout<<"age: "<<age<<endl;
		cout<<"title: "<<title<<endl;
	}
};
class Student
{
protected:
	string name1;
	char sex;
	float score;
public:
	Student(char nam[],char s,float sco)
	{
		strcpy(name1,nam);
		sex = s;
		score = sco;
	}
	void display1()
	{
		cout<<"name: "<<name1<<endl;
		cout<<"sex: "<<sex<<endl;
		cout<<"score: "<<score<<endl;
	}
};
class Graduate:public Teacher,public Student
{
private:
	float wage;
public:
	Graduate(string nam,int a,char s,string t,float sco,
		float w):Teacher(nam,a,t),Student(nam,s,sco),wage(w){}
	void show()
	{
		cout<<"name: "<<name<<endl;
		cout<<"age: "<<age<<<endl;
		cout<<"sex: "<<sex<<endl;
		cout<<"score: "<<score<<endl;
		cout<<"title: "<<title<<endl;
		cout<<"wages: "<<wage<<endl;
	}
};
int main()
{
	Graduate grad1("Wang_li",24,'f',"assistant",89.5,2400);
	grad1.show();
	return 0;
}

5.6.3 多重继承引起的二义性问题
基类的同名成员在派生类中被屏蔽,成为“ 不可见的 ”,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。因此如果在定义派生类对象的模块中通过对象名访问同名的成员则访问的是派生类的成员。请注意:不同的成员函数,只有在函数名和参数个数相同,类型相匹配的情况下才发生同名覆盖,如果只有函数名相同而参数不同,不会发生同名覆盖,而属于函数重载。
5.6.4 虚基类
1,虚基类的作用
如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性。
C++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。
注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式声明的。
声明虚基类的一般形式:
class 派生类名:virtual 继承方式 基类名
为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。
2,虚基类的初始化
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。C++编译系统只执行最后派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
3,虚基类的简单应用举例

#include<iostream>
#include<string>
using namespace std;
class Person
{
protected:                    //保护成员
	string name;
	char sex;
	int age;
public:
	Person(string nam,char s,int a)     //构造函数
	{
		name = nam;
		sex = s;
		age = a;
	}
};
class Teacher:virtual public Person       //声明Person为公用继承的虚基类
{
protected:
	string title;
public:
	Teacher(string nam,char s,int a,string t):Person(nam,s,a)
	{
		title = t;
	}
};
class Student:virtual public Person   //声明Person为公用继承的虚基类
{
protected:
	float score;
public:
	Student(string nam,char s,int a,float sco):Person(nam,
		s,a),score(sco){}
};
//下面声明多重继承的派生类Graduate
class Graduate:public Teacher,public Student //Teacher和Student为直接基类
{
private:
	float wage;
public:
	Graduate(string nam,char s,int a,string t,float sco,
		float w):Person(nam,s,a),Teacher(nam,s,a,t),
		Student(nam,s,a,sco),wage(w){}
	void show()
	{
		cout<<"name: "<<name<<endl;
		cout<<"age: "<<age<<endl;
		cout<<"sex: "<<sex<<endl;
		cout<<"score: "<<score<<endl;
		cout<<"title: "<<title<<endl;
		cout<<"wages: "<<wage<<endl;
	}
};
int main()
{
	Graduate grad1("Wang_li",'f',24,"assistant",89.5,1200);
	grad1.show();
	return 0;
}

5.7 基类与派生类的转换
<1>派生类对象可以向基类对象赋值。只能用子类对象对其基类对象赋值,而不能用基类对象对其子类对象赋值。
<2>派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。
<3>如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。
<4>派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,向基类对象的指针变量也可以用来指向派生类对象。
5.8 继承与组合
在一个类中以另一个类的对象作为数据成员的,成为类的组合。
继承是纵向的,组合是横向的。
5.9 继承在软件开发中的重要意义
继承是C++和C的最重要的区别之一。
第 5 章课后习题详解链接待补充

                       #第  6  章  多态性和虚函数

6.1 什么是多态性
多态性:向不同的对象发送同一个消息,不同的对象在接受时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
<1>在C++中,多态性表现形式之一的是:具有不同功能的函数可以用同一函数名,这样就可以用一个函数名调用不同内容的函数。
<2>从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。
<3>静态多态性是通过函数的重载实现的。
<4>动态多态性又称为运行时的多态性。动态多态性是通过虚函数实现的。
<5>派生类对象可以替代基类对象向基类对象的引用初始化或赋值。
<6>两个同名函数不在同一个类中,而是分别在基类和派生类中,属于同名覆盖。
6.3 利用虚函数实现动态多态性
6.3.1 虚函数的作用
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数

#include<iostream>
#include<string>
using namespace std;
class Student          //声明基类Student
{
protected:
	int num;
	string name;
	float score;
public:
	Student(int,string,float);
	virtual void display();      //把Student类的display函数声明为虚函数
};
Student::Student(int n,string nam,float s)
{
	num = n;
	name = nam;
	score = s;
};
void Student::display()
{
	cout<<"num: "<<num<<"\nname: "<<name<<"\nscore: "<<score<<"\n\n";
}
class Graduate:public Student       //声明公用派生类Graduate
{
private:
	float wage;
public:
	Graduate(int,string,float,float);
	void display();
};
Graduate::Graduate(int n,string nam,float s,float w):Student(n,nam,s),
wage(w){}
void Graduate::display()
{
	cout<<"num: "<<num<<"\nname: "<<name<<"\nscore: "<<score<<
		"\nwage: "<<wage<<endl;
}
int main()
{
	Student stud1(1001,"Li",87.5);
	Graduate grad1(2001,"Wang",98.5,1200);
	Student *pt = &stud1;
	pt->display();
	pt = &grad1;
	pt->display();
	return 0;
}

虚函数的使用方法:
<1>在基类中用virtual声明成员函数为虚函数。在类外定义虚函数时,不必再加virtual。
<2>当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。在派生类中重新定义此函数,函数名,函数类型,函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体。
<3>定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
<4>通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
<5>如果想调用同一类族中不同类的同名函数,只要先用基类指针指向该类对象即可。
6.3.2 静态关联与动态关联
在编译时即可确定其调用的虚函数属于哪一个类,其过程称为静态关联。
在运行阶段把虚函数和类对象“ 绑定 ”在一起的,因此,此过程称为动态关联。
6.3.3 在什么情况下应当声明虚函数
<1>只能用virtual声明类的成员函数,把它作为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。
<2>一个成员函数被声明为虚函数后,在同一类族中的类就不能在定义一个非virtual的但于该虚函数具有相同的参数
6.3.4 虚析构函数
在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。
如果希望执行派生类circle的析构函数,可以将基类的析构函数声明为虚析构函数。
构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上把函数与类对象的绑定。
6.4 纯虚函数与抽象类
6.4.1 纯虚函数
声明纯虚函数的一般形式是
virtual 函数类型 函数名(参数表列)=0;
纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便在派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。
6.4.2 抽象类
不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类,由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不可能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。
6.4.3 应用实例

#include<iostream>
using namespace std;
class Shape                   //声明抽象基类Shape
{
public:
	virtual float area() const {return 0.0;}
	virtual float volume() const {return 0.0;}
	virtual void shapeName() const = 0;
};
class Point:public Shape      //Point是Shape的公用派生类
{
protected:
	float x,y;
public:
	Point(float = 0,float = 0);
	void setPoint(float,float);
	float getX() const {return x;}
	float getY() const {return y;}
	virtual void shapeName() const {cout<<"Point: ";}
	friend ostream & operator << (ostream &,const Point &);
};
Point::Point(float a,float b)
{x = a;y = b;}
void Point::setPoint(float a,float b)
{x = a;y = b;}
ostream & operator << (ostream & output,const Point &p)
{
	output<<"["<<p.x<<","<<p.y<<"]";
	return output;
}
class Circle:public Point           //声明Circle类
{
protected:
	float radius;
public:
	Circle(float x=0,float y=0,float r=0);
	void setRadius(float);
	float getRadius() const;
	virtual float area() const;
	virtual void shapeName() const {cout<<"Circle: ";}
	friend ostream &operator << (ostream &,const Circle &);
};
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){}
void Circle::setRadius(float r):radius(r){}
float Circle::getRadius() const {return radius;}
float Circle::area() const {return 3.14159*radius*radius;}
ostream &operator << (ostream &output,const Circle &c)
{
	output<<"["<<c.x<<","<<c.y<<"],r="<<c.radius;
	return output;
}
class Cylinder:public Circle
{
protected:
	float height;
public:
	Cylinder(float x=0,float y=0,float r=0,float h=0);
	void setHeight(float);
	virtual float area() const;
	virtual float volume() const;
	virtual void shapeName() const {cout<<"Cylinder:";}
	friend ostream & operator << (ostream&,const Cylinder&);
};
Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),
height(h){}
void Cylinder::setHeight(float h){height = h;}
float Cylinder::area() const
{return 2*Circle::area()+2*3.14159*radius*height;}
float Cylinder::volume() const
{return Circle::area()*height;}
ostream &operator << (ostream &output,const Cylinder &cy)
{
	output<<"["<<cy.x<<","<<cy.y<<"],r="<<cy.radius<<",h="<<cy.height;
	return output;
}
int main()
{
	Point point(3.2,4.5);
	Circle circle(2.4,1.2,5.6);
	Cylinder cylinder(3.5,6.4,5.2,10.5);
	point.shapeName();
	cout<<point<<endl;
	circle.shapeName();
	cout<<cylinder<<endl<<endl;
	Shape *pt;
	pt = &point;
	pt->shapeName();
	cout<<"x= "<<point.getX()<<",y= "<<point.getY()<<"\narea= "<<pt->area()<<
		"\nvolume = "<<pt->volume()<<"\n\n";     //输出点的数据
	pt = &circle;
	pt->shapeName();
	cout<<"x= "<<circle.getX()<<",y= "<<circle.getY()<<"\narea= "<<pt->area()
		<<"\nvolume= "<<pt->volume()<<"\n\n";    //输出圆的数据
	pt = &cylinder;
	pt->shapeName();
	cout<<"x= "<<cylinder.getX()<<",y= "<<cylinder.getY()<<"\narea= "<<
		pt->area()<<"\nvolume()"<<pt->volume()<<"\n\n";
	return 0;
}


第 6 章课后习题详解链接待补充

                       #**第  7  章  输入输出流**

7.1 C++的输入输出
7.1.1 输入输出的含义
<1>对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为“ 标准的输入输出 ”,简称标准I/O。
<2>以外存(磁盘,光盘)为对象进行的输入和输出,例如从磁盘文件输入数据,数据输出到磁盘文件。近年来已用光盘文件作为输入文件。这种以外存文件为对象的输入输出称为文件的输入输出,简称文件I/O。
<3>对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何类型的信息)。这种输入和输出称为字符串输入输出,简称串I/O
7.1.2 C++的I/O对C的发展——类型安全和可扩展性
C语言采用函数实现输入输出(如scanf和printf函数),C++采用类对象来实现输入输出(如cin,cout)
7.1.3 C++的输入输出流
当用cout和插入运算符" << "向显示器输出数据时,先将这些数据插入到输出流,送到输出缓冲区保存,直到缓冲区满了或遇到endl,就将缓冲区中的全部数据送到显示器显示出来。在输入时,从键盘输入的数据先放在键盘的缓冲区,当键入回车符时,键盘缓冲区中的数据输入到计算机的输入缓冲区,形成cin流,然后用提取运算符“ >> ”从输入缓冲区中提取数据送给程序中的有关变量。
1,C++的流库
2,与流库有关的头文件
——iostream 包含了对输入输出流进行操作所需的基本信息。
——fstream 用于用户管理的文件的I/O操作
——strstream 用于字符串流I/O
——stdiostream 用于混合使用C和C++的I/O机制时,例如想将C程序转变为C++程序。
——iomanip 在使用格式化I/O时应包含此头文件
3,在iostream头文件中定义的流对象
4,在iostream头文件中重载运算符
7.2 标准输出流
标准输出流是流向标准输出设备(显示器)的数据。
7.2.1 cout,cerr,clog流
1,cout流对象
cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插入一个endl时,不论缓冲区是否已满,都立即输出流中所有数据,然后插入一个换行符,并刷新流(清空缓冲区)。注意如果插入一个换行符’ \n ‘(如cout<<a<<’ \n ';),则只输出a和换行,而不刷新cout流(但并不是所有编译系统都体现出这一区别)
2,cerr流对象
cerr流对象是标准出错信息流。
3,clog流对象
clog流对象也是标准出错流。
注意:cerr是不经过缓冲区直接向显示器上输出有关信息,而clog中的信息存放在缓冲区,缓冲区满后或遇endl时向显示器输出。
7.2.2 标准类型数据的格式输出
1,使用控制符控制输出格式
2,用流对象的成员函数控制输出格式
成员函数width(n)和控制符setw(n)只对其后的第一个输出项有效。
7.2.3 用流成员函数put输出字符
专用于输出单个字符的成员函数put。
7.3 标准输入流
标准输入流是从标准输入设备(键盘)流向计算机内存的数据。
7.3.1 cin流
7.3.2 用于字符输入的流成员函数
1,用get函数读入一个字符
<1>不带参数的get函数
cin.get()
<2>有一个参数的get函数
cin.get(ch)
<3>有3个参数的get函数
cin.get(字符数组,字符个数n,终止字符)

cin.get(字符指针,字符个数n,终止字符)
2,用成员函数getline函数读入一行字符
cin.getline(字符数组(或字符指针),字符个数n,终止标志字符)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值