清华郑莉C++语言程序设计学习笔记(2)-类与对象、数据的共享与保护

10 篇文章 7 订阅

第四章 类与对象

4.1 导学

对象:现实中对象的模拟,具有属性和行为。定义对象时,通过构造函数初始化;删除对象时,通过析构函数释放资源。
:同一类对象的共同属性和行为。
结构体、联合体、枚举类

4.2 面向对象程序的基本特点

抽象:对同一类对象的共同属性和行为进行概括,形成类。首先注意问题的本质及描述,其次是实现过程和细节。
数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。int hour, int minute, int second
代码抽象:描述某类对象的共有的行为特征或具有的功能。 setTime(), showTime()
抽象的实现:类

# include <iostream>
using namespace std;
class Clock{
public:
	void setTime(int newH=0, int newM=0, int newS=0);
	void showTime();
private:
	int hour, minute, second;
}
void Clock::setTime(int newH, int newM, int newS){
	hour = newH;
	minute = newM;
	second = newS;
}
void Clock::showTime(){
	cout << hour << ":" << minute << ":" << second;
}

封装:将抽象出的数据、代码封装在一起,形成类。
目的:增强安全性和简化编程,使用者不必了解具体的实现细节,只需要通过外部接口,以特定的访问权限,来使用类的成员。
实现封装:类声明中的{},public: 外部接口,private: 被隐藏在内部
继承:在已有类的基础上,进行扩展形成新的类。
多态:同一名称,不同的功能实现方式。目的:达到行为标识统一,减少程序中标识符的个数。

4.3 类和对象

4.3.1 类和对象的定义

设计类就是设计类型:合法值?什么样的函数和操作符?如何被创建和销毁?如何初始化和赋值?作为函数参数如何以值传递?谁将使用此类型的对象成员?

class 类名称{
	public:
		公有成员(外部接口,任何外部函数都可以访问公有类型数据和函数)
	private:
		私有成员(只允许本类中的函数访问,类外部的任何函数都不能访问。如果紧跟在类名称的后面声明私有成员,则关键字private可以省略)
	protected:
		保护型成员(与private类似,其差别表现在继承与派生时对派生类的影响不同)
}

为数据成员设置类内初始值,用于初始化数据成员。
对象定义的语法:类名 对象名 Clock myClock;
访问成员:类中成员之间直接使用成员名互相访问;从类外访问成员使用“对象名.成员名”方式访问public成员。
类的成员函数:在类中声明函数原型;可以在类外给出函数体实现,并在函数名前使用类名加以限定;也可以直接在类中给出函数体,形成内联成员函数;允许声明重载函数和带默认参数值的函数。
内联成员函数:为了提高运行效率,对于简单函数可以声明为内联形式。内联函数体中不要有复杂结构(循环和switch)。在类中声明内联成员函数的方式:将函数体放在类的声明中/使用inline关键字。

4.3.2 类和对象的程序举例

使用时,需使用实例。

int main(){
	Clock myClock;
	myClock.setTime(8.30,30);
	myClock.showTime();
	return 0;
}

4.4 构造函数

4.4.1 构造函数的基本概念

构造函数:类中的特殊函数,用于描述自定义类型的初始化算法,将对象初始化为一个特定的初始状态。
构造函数的形式:函数名与类名相同;不能定义返回值类型,也不能有return语句;可以有形式参数,也可以没有形式参数;可以是内联函数;可以重载;可以带默认参数值。
构造函数调用时机:在对象创建时自动被调用。例如:Clock myClock(0,0,0);
默认构造函数:调用时可以不需要实参的构造函数(参数表为空的构造函数,全部参数都有默认值的构造函数),下面两个都是默认构造函数,如果在类中同时出现,不是合法的函数默认的重载形式,将产生编译错误。

Clock();
Clock(int newH=0, int newM=0; int newS=0);

隐含生成的构造函数:如果程序中未定义构造函数,编译器将自动生成一个默认构造函数。
1、参数列表为空,不为数据成员设置初始值;
2、如果类内定义了成员的初始值,则使用类内定义的初始值;
3、如果没有定义类内的初始值,则以默认方式初始化;
4、基本类型的数据默认初始化的值是不确定的。
“==defaut”:如果程序已定义构造函数,默认情况下编译器不再隐含生成默认构造函数,如果此时依然希望编译器隐含生成默认构造函数,可以使用Clock()=default 。例如,程序员编程时不介意初始值是什么。

例:时钟类

class Clock{
public:
	Clock(int newH, int newM, int newS); //构造函数
	Clock(); //默认构造函数
	void setTime(int newH=0, int newM=0, int newS=0);
	void showTime();
private:
	int hour, minute, second;
}
//构造函数实现
Clock::Clock(int newH, int newM, int newS):
	hour(newH),minute(newM),second(newS){} //用初始化列表方式实现简单赋值
Clock::Clock():hour(0),minute(0),second(0){}//默认构造函数,用0初始化

int main(){
	Clock c1(8,10,0);//自动调用有参数的构造函数
	Clock c2; //调用无参数的构造函数
	c1.showTime();
	return 0;
}
4.4.2 委托构造函数

上例的两个Clock构造函数:

Clock(int newH, int newM, int newS):
	hour(newH),minute(newM),second(newS){} //用初始化列表方式实现简单赋值
Clock():Clock(0,0,0){}//无参数的构造函数,将默认参数传给有参数表的Clock

优点:不用把重复的初始化过程重复写;保持代码实现的一致性,修改代码时只需要在一处修改。

4.4.3 复制构造函数

定义:是一种特殊的构造函数,其形参为本类的对象引用。描述如何用一个已经存在的对象去初始化一个同类型的新对象,用它的引用作为构造函数的参数。

class 类名{
	public:
		类名 (形参); //构造函数
		类名 (const 类名 &对象名); //复制构造函数 常引用,只能使用该引用读取数据,但不能用它指向的对象对它进行修改,保证实参的安全性
}
类名::(const 类名 &对象名) //复制构造函数的实现
{函数体}

复制构造函数被调用的三种情况
1、定义一个对象时,以本类另一个对象作为初始值,发生复制构造;
2、如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;
3、如果函数返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。这种情况也可以通过移动构造避免不必要的复制。

int main(){
	Point a; //第一个对象A
	Point b(a); //情况1,用A初始化B
	fun1(b); //情况2,对象B作为fun1的实参
	b = fun2(); //情况3,函数的返回值是类对象
	return 0;
}

隐含的复制构造函数:如果程序没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数。这个构造函数执行的功能是:用初始值对象的每个数据成员,初始化将要建立的对象的对应数据成员。
“=delete”:不希望对象被复制构造。例:

class Point{
public:
	Point(int xx=0, int yy=0){x = xx; y = yy;}; //构造函数,内联
	Point(const Point& p)=delete; //指示编译器不生成默认复制构造函数
private:
	int x,y; //私有数据
}

4.5 析构函数

功能:析构函数完成对象被删除前的一些清理工作。在对象的生存期结束的时刻系统自动调用析构函数。如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数,其函数体为空。
析构函数的原型: ~类名();
析构函数没有参数,没有返回类型,没有return语句。

4.6 类的组合

4.6.1 类的组合

组合的概念:类中的成员是另一个类的对象,可以在已有抽象的基础上实现更复杂的抽象。
类组合的构造函数设计原则:不仅要负责对本类中的基本类型成员数据初始化,也要对部件对象成员初始化。声明形式:

类名::类名(对象成员所需的形参,本类成员形参):对象1(参数),对象2(参数),...{
//函数体其他语句
}

构造组合类对象时的初始化次序
1、首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类内中定义的次序。
成员对象构造函数的调用顺序:按对象成员的定义顺序(不是初始化列表出现顺序),先声明者先构造。初始化列表中未出现的成员对象,调用默认构造函数(即无形参的)初始化。
2、处理完初始化列表之后,再执行构造函数的函数体。
重用定义好的类注意事项
1、如果组合类不写构造函数,也就无法给部件对象传参数,因此在定义部件对象时记得加上默认构造函数
2、写组合类的时候,也要参考部件对象的接口说明,看看是否需要传参数。

例:

class Point{
public:
	Point(int xx=0, int yy=0){x = xx; y = yy;}; //构造函数,内联
	Point(const Point& p); //指示编译器不生成默认复制构造函数
	int getX(){return x;}
	int getY(){return y;}
private:
	int x,y; //私有数据
}
Point::Point(Point &p){//拷贝构造函数的实现
	x = p.x;
	y = p.y;
	cout << "Calling the copy constructor of Point" << endl;
}
//类的组合
class Line{//Line类的定义
public: //外部接口
	Line(Point xp1, Point xp2);
	Line(Line &l);
	double getLen() {return len;}
private: //私有数据成员
	Point p1,p2; //Point类的对象p1,p2
	double len;
};

//组合类的构造函数
Line::Line(Point xp1, Point xp2):p1(xp1),p2(xp2){
	cout << "Calling constructor of Line" << endl;
	double x = static_cast<double>(p1.getX() - p2.getX());
	double y = static_cast<double>(p1.getY() - p2.getY());
	len = sqrt(x*x + y*y);
}

//组合类的拷贝构造函数
Line::Line(Line &l):p1(l.p1),p2(l.p2){
	cout << "Calling the copy constructor of Line" << endl;
	len = l.len;
}
//主函数
int main(){
	Point myp1(1,1), myp2(4,5); //建立Point类的对象
	Line line(myp1, myp2); //建立Line类的对象
	Line line2(line);  //利用拷贝构造函数建立一个新对象
	cout << "The length of the line is:" ;
	cout << line.getLen() << endl;
	cout << "The length of the line2 is:";
	cout << line2.getLen() << endl;
	return 0;
}
4.6.2 前向引用声明

类应该先声明,后使用。如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。前向引用声明只为程序引入一个标识符,但具体声明在其他地方。

class B; //前向引用声明
class A{
public:
	void f(B,b);
};
class B{
public:
	void g(A,a);
}

前向引用声明注意事项:在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。

class Fred; //前向引用声明
class Barney{
	Fred x; //错误:类Fred的声明尚不完善
};
class Fred{
	Barney y;
}

4.7 UML简介

UML的三个基本部分:事物(Things)、关系(Relationships)、图(Diagrams)
类图:
在这里插入图片描述
对象图:
在这里插入图片描述
依赖关系:
在这里插入图片描述
关联:
在这里插入图片描述
聚集:
在这里插入图片描述
用UML图描述Line类和Point类的关系(带有注释):
在这里插入图片描述
继承关系——泛化:
在这里插入图片描述

4.8 结构体与联合体

4.8.1 结构体

结构体是一种特殊形态的类,与类的唯一区别:类的缺省访问权限是private,结构体的缺省访问权限是public。
什么时候用结构体而不用类:定义主要用来保持数据,而没有什么操作的类型。人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便。
定义

struct 结构体名称{
	公有成员
protected:
	保护型成员
private:
	私有成员
};

结构体中可以有数据成员和函数成员。
结构体的初始化:如果一个结构体的全部数据成员都是公共成员,没有用户定义的构造函数,没有基类和虚函数,这个结构体的变量可以用以下语法形式初始化:
类型名 变量名 = {成员数据1初值, 成员数据2初值,…};

struct Student{//学生信息结构体
	int num;  //学号
	string name;  //姓名,字符串对象
	char sex;  //性别
	int age;  //年龄
}
int main(){
	Student stu = {97001, "Lin Lin", 'F', 19};  //初始化
	cout << "Num: " << stu.num << endl;  //访问
	return 0;
}
4.8.2 联合体

定义

union 联合体名称{
	公有成员
protected:
	保护型成员
private:
	私有成员
};

特点:成员共用同一组内存单元,任何两个成员不会同时有效。
联合体的内存分配
在这里插入图片描述
无名联合

union{
	int i;
	float f;
}

在程序中可以 i = 10; f = 2.2,但前面的赋值会被后面的冲掉。

例:使用联合体保存成绩并输出
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.9 枚举类/强类型枚举

简单枚举类型:整数的一个子集
C++新标准枚举类:语法 enum class 枚举类型名: 底层类型 {枚举值列表};

enum class Type {General, Light, Medium, Heavy};
enum class Type:char {Genral, Light, Medium, Heavy};
enum class Category {Genral=1, Pistol, MachineGun, Cannon};

枚举类优势
1、强作用域(其作用域限制在枚举类中),例:使用Type的枚举值General,Type::General 避免不同类中枚举值重名的问题。
2、转换限制:枚举类对象不可以与整型隐式地互相转换。
3、可以指定底层类型。

例:
在这里插入图片描述

4.10 实验四

# include <iostream>
using namespace std;

enum CPU_Rank {P1=1, P2, P3, P4, P5, P6, P7};
class CPU //声明CPU类
{
private:// 包含私有数据成员:等级、频率、电压
	CPU_Rank rank; 
	int frequency;
	float voltage;
public:
	CPU(CPU_Rank r, int f, float v){//构造函数
		rank = r;
		frequency = f;
		voltage = v;
		cout << "构造了一个CPU!" << endl;
	}
	~CPU() {cout << "构造了一个CPU!" << endl;}  //析构函数
	CPU_Rank GetRank() const {return rank;};
	int GetFrequency() const {return frequency;}
	float GetVoltage() const {return voltage;}
	
	void SetRank(CPU_Rank r){rank = r;}
	void SetFrequency(int f){frequency = f;}
	void SetVoltage(float v){voltage = v;};
	void Run() {cout << "CPU开始运行!" << endl;}
	void Stop() {cout << "CPU停止运行!" << endl;}
}

已定义类CPU, RAM, CD_ROM,定义一个类的组合COMPUTER

class COMPUTER
{
private:
	CPU my_cpu;
	RAM my_ram;
	CD_ROM my_cdrom;
	unsigned int storage_size; //GB
	unsigned int bandwidth; //MB
public:
	COMPUTER(CPU c, RAM r, CD_ROM cd, unsigned int s, unsigned int b);
	~COMPUTER(){cout<<"析构了一个COMPUTER!"<<endl;}
}
COMPUTER:COMPUTER(CPU c, RAM r, CD_ROM cd, unsigned int s, unsigned int b):my_cpu(c),my_ram(r),my_cdrom(cd){ //对象分别调用拷贝构造函数完成初始化
	storage_size = s;
	bandwidth = b;
	cout << "构建了一个COMPUTER!" << endl;
}

第五章 数据的共享与保护

5.1 导学

变量和对象定义在不同的位置(函数体内、类体内、函数原型参数表内、所有函数和类之外)。其作用域可见性生存期都不同。
静态数据成员:属于整个类的数据成员
静态成员函数:用于处理静态数据成员的函数
友元:对一些类外的函数、其他的类,给预授权,使之可以访问类的私有成员。通过const关键字,限制对共享数据的修改。
编译预处理:让编译器按要求编译程序的不同部分。
多文件结构:将程序分成多个文件,组织在一个工程。

5.2 标识符的作用域与可见性

作用域分类:函数原型作用域、局部作用域(块作用域)、类作用域、文件作用域、命名空间作用域
函数原型作用域:函数原型中的形参,其作用域为“()”内。
局部作用域(块作用域):函数的形参、在块中声明的标识符,作用域自声明处起,限于块中。
类作用域:类的成员,其范围包括类体和成员函数体。
在类作用域以外访问类的成员:
1、静态成员:通过类名,或者该类的对象名、对象引用访问。
2、非静态成员:通过类名,或者该类的对象名、对象引用、对象指针访问。
文件作用域:不在前述各个作用域中出现的声明,开始于声明点,结束于文件尾。
可见性:从标识符的引用角度的概念,表示从内层作用域向外层作用域“看”时能看见什么,如果标识在某处可见,就可以在该处引用该标识符。如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见;对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见

5.3 对象的生存期

静态生存期:与程序的运行期相同,只有程序结束才会释放。在文件作用域中声明的对象,具有这种生存期。在函数内部声明静态生存期对象,要冠以关键字static。
动态生存期:开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。块作用域中声明的,没有用static修饰的对象,是动态生存期的对象(称局部生存期对象)。

5.4 类的静态成员

5.4.1 静态数据成员

用关键字static声明,为该类的所有对象共享,静态数据成员具有静态生存期,必须在类外定义和初始化,用(::)来指明所属的类。静态数据成员的引用,注意加上类名::来修饰。
在这里插入图片描述
在这里插入图片描述

5.4.2 静态函数成员

静态函数成员用于处理该类的静态数据成员。如果访问非静态成员,要通过对象来访问。
在这里插入图片描述
在这里插入图片描述
静态函数成员既可以通过类::函数名访问,也可以通过对象::函数名访问。

5.5 类的友元

友元定义:是C++提供的一种破坏数据封装和数据隐藏的机制。通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是被隐藏的信息。可以声明友元函数和友元类。为了确保数据的完整性,及数据封装与隐藏的原则,建议慎用友元。
友元函数:在类声明中由关键字friend修饰说明的非友元函数,在它的函数体中能够通过对象名访问private和protected成员。作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。访问对象中的成员必须通过对象名。
在这里插入图片描述
在这里插入图片描述
友元类:若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。声明语法:将友元类名在另一个类中使用friend修饰说明。
在这里插入图片描述
类的友元关系是单向的:声明B类是A类的友元 ≠ A类是B类的友元

5.6 共享数据的保护

常类型
常对象:用const修饰的对象,必须进行初始化,不能被更新 const 类名 对象名;
在这里插入图片描述
常成员:用const进行修饰的类成员,包括常数据成员和常函数成员。
常函数成员:使用const关键字说明的函数,常成员函数不更新对象的数据成员。说明形式:类型说明符 函数名(参数表) const。这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。const关键字可以被用于参与对重载函数的区分,通过常对象只能调用它的常成员函数
在这里插入图片描述
在这里插入图片描述
常数据成员:使用const说明的数据成员。
在这里插入图片描述
在这里插入图片描述

常引用:被引用的对象不能被更新。在友元函数中用常引用做参数,既能获得较高的执行效率,又能保证实参的安全性。 const 类型说明符 &引用名;

例5-9 常引用作形参
在这里插入图片描述
在这里插入图片描述
常数组:数组元素不能被更新 类型说明符 const 数组名[大小]
常指针:指向常量的指针

5.7 多文件结构和预编译命令

C++程序的一般组织结构:一个工程可以划分为多个源文件,例如:类声明文件(.h文件)、类实现文件(.cpp文件)、类的使用文件(main()所在的.cpp文件),利用工程来组合各个文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
外部变量:除了在定义它的源文件中可以使用外,还能被其它文件使用。文件作用域中定义的变量,默认情况下都是外部变量。在其他文件中如果需要使用,需要用extern关键字声明。
外部函数:在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。这样的函数可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。
将变量和函数限制在编译单元内:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。

namespace{//匿名的命名空间
	int n;
	void f(){
		n++;
	}
}

标准C++库:一个极为灵活并可扩展的可重用软件模块的集合。标准C++类与组件在逻辑上分为6种类型:输入/输出类,容器类与抽象数据类型,存储管理类,算法,错误处理,运行环境支持。
编译预处理
#include包含指令 将一个源文件嵌入到当前源文件中该点处
#include<文件名> 按标准方式搜索,文件位于C++系统目录的include子目录下
#include"文件名" 首先在当前目录中搜索,若没有,再按标准方式搜索
#define宏定义指令 定义符号常量,很多情况下已被const定义语句取代;定义带参数宏,已被内联函数取代
#undef 删除由#define定义的宏,使之不再起作用
条件编译指令 #if和#endif

#if 常量表达式
//当“常量表达式”非0时编译
	程序正文
#endif
#ifdef 标识符
	程序段1
#else
	程序段2

如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1,否则编译程序段2;如果“标识符”未被定义过,则编译程序段1,否则编译程序段2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值