C++学习笔记——基础(二)

第五章 类与对象

面向对象程序的基本特点

  • 抽象:对同一类对象的共同属性和行为概括,形成类
  • 封装{}:将抽象出的数据、代码封装在一起,形成类
  • 继承:在已有类的基础上,扩展形成新的类
  • 多态:统一名称,不同功能的实现方式

类与对象

类的语法形式

class 类名称
{
  public:
      公有成员(外部接口)
  private:
      私有成员(只允许本类中的函数访问,
                 类外部的任何函数都不能访问)
  protected:
      保护型成员
};
  • 对象定义的语法
    类名 对象名; clock myClock
  • 类的成员函数
    在类中声明函数原型;
    可以在类外给出函数体实现,并在函数名前使用类名加以限定
    也可以直接在类中给出函数体,形成内联函数成员;
    允许声明重载函数和带默认参数值的函数。

程序举例
钟表类定义

#include<iostream>
using namespace std;
class Clock{
public:
  void setTime(int newH = 0,int newM = 0,int newS = 0);
//当调用setTime时,如果有实参则用实参值,否则用默认。
  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;
}

对象的使用

int main(){
  Clock myClock;
  myClock.setTime(8,30,30);//与myClock通信,设定时间
  myClock.showTime();//显示时间
  return 0;
}

构造函数

类中的构造函数,用于描述初始化算法。

  • 构造函数的作用
    在构造函数中描述,如何对类的对象进行初始化,把初始化的规则算法写在构造函数中。
  • 构造函数的形式
    ①函数名与类名相同
    ②不能定义返回值类型,也不能有return语句
    ③可以有形式参数,也可以没有
    ④可以是内联函数
    ⑤可以重载
    ⑥可以带默认值
  • 构造函数调用机制:在对象被创建时自动调用
  • 默认构造函数:调用时可以不需要实参
  • 隐含生成的构造函数:若程序中未定义构造函数,编译器将自动生成一个默认构造函数
    ①参数列表为空,不为数据成员设置初始值
    ②若类内定义了成员的初始值,则使用默认值,否则,以默认方式初始化
    ③基本类型的数据默认初始化的值是不确定的

若程序中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。若希望生成默认构造函数可以使用 “=default”

class Clock{
public:
  Clock() =default;  //提供默认构造函数
  Clock(int newH, int newM, int newS);   //构造函数
privateint hour, minute, second;
  }

例题

//类定义
class Clock{
public:
  Clock(int newH,int newM, int newS);
  //构造函数的原型声明,与类名一致,但是不能定义其返回类型!!
  void setTime(int newH,int newM, int newS);
  void showTime();
private:
  int hour, minute, second;
}
//构造函数的实现:
//写类名,不是全局函数,是类的成员函数。
//不能规定返回类型,没有return语句!
Clock::Clock(int newH, int newM, int newS):
hour(newH), minute(newM),second(newS){} 
//此行为初始化列表,表示用newH初始化hour这个变量...
//比在函数体中写表达式赋值效率高

//主函数定义
int main(){
Clock c(0,0,0);//自动调用构造函数
c.shouTime();
}

另例:

class Clock{
public:
  Clock(int newH,int newM, int newS);  //构造函数
  Clock();  //默认构造函数
  void setTime(int newH,int newM, int newS);
  void showTime();
private:
  int hour, minute, second;
};
Clock::Clock():hour(0), minute(0), second(0){}  
//默认构造函数

int main(){
  Clock c1(8,10,0);  //调用有参数的构造函数
  Clock c2;  //调用无参数的构造函数
  ... ...
 }

委托构造函数
使用类的其他构造函数执行初始化过程

复制构造函数
形参必须是本类的对象引用。
作用是用一个已经存在的对象去初始化同类型的新对象。

结构:

class 类名 {
public:
类名(形参);//构造函数
类名(const 类名&对象名);//复制构造函数
//不允许定义函数名 也不能有return语句
//...
};
类名::类(const 类名&对象名) //复制构造函数的实现
{ 函数体 }

在引用时,是双向传递的,但在初始化时不希望新对象对原引用对象修改。
因此加 const,说明引用是常引用,只能使用引用去读取数据,但是不能用引用对其指向的对象进行修改。这样能同时保证引用,以及实参的安全性。

复制构造函数被调用的情况

  1. 定义一个对象时,以本类另一个对象做为初始值,发生复制构造
  2. 如果函数的型参是类的对象,调用函数时,将使用实参对象初始化形参对象,形实结合,发生复制构造
  3. 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化另一个临时无名对象,传递给主调函数,发生复制构造

隐含的复制构造函数

  • 如果没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数
  • 此构造函数的执行功能: 用初始值对象的每个数据成员,初始化将要建立对象的对应数据成员

若不希望对象被复制构造,用 “=delete” 指示编译器不生成默认复制构造函数

析构函数

  • 析构函数完成对象被删除前的清理工作
  • 在对象的生存期结束的时刻,系统自动调用析构函数
  • 如果程序中未声明析构函数,编译器将自动生成一个默认的析构函数,其函数体为空

析构函数的原型:~类名();
析构函数没有参数,没有返回类型

定义一个内联成员函数提高程序的执行效率
用已有的类初始化一个新对象,需要一个复制构造函数

类的组合

  • 类中的成员是另一个类的对象
  • 可以在已有的抽象基础上实现更复杂的抽象

类组合的构造函数设计

  • 原则:不仅要对负责本类的基本类型成员数据初始化,也要对对象成员初始化
  • 声明形式
    类名::类名(对象成员所需的形参,本类成员形参):
    对象1(参数),对象2(参数),...
    {
    //函数体其他语句
    }

构造组合类对象时初始化次序

  1. 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序
    成员对象构造函数调用顺序:按对象成员的定义顺序,先声明者构造;
    初始化列表中未定义的成员,调用默认构造函数(即无形参的)初始化

  2. 处理完初始化列表后,再执行构造函数的函数体

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

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

UML 图形建模语言

  • 事物things
  • 关系relationships
  • 图diagrams

结构体 联合体

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

结构体定义

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

联合体:所有成员共用相同存储单元

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

枚举类

定义语法形式 enum class 枚举类型名:底层类型{枚举值列表};
例如:
enum class Type {General, Light, Medium, Heavy};
enum class Type: char {General, Light, Medium, Heavy};
enum class Category {General=1, Pistol, MachineGun, Cannon};

枚举类优势:

  • 强作用域:将作用于限制在枚举类中
    枚举时必须带上类名Type::General
  • 转换限制:
  • 可指定底层类型

实验四

#eg1:声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,有两个公有成员run、stop。
其中rank为枚举类型CPU_rank,声明为 enum CPU_rank {P1=1,P2,P3,P4,P5,P6,P7},frequency单位为MHz的整型数,voltage为浮点型的电压值。
注意不同的访问属性的成员访问方式,并观察构造函数和析构函数的调用顺序。

#include <iostream>
using namespace std;

enum CPU_Rank { P1 = 1, P2, P3, P4, P5, P6, P7 };
//声明CPU类,包含私有数据成员 rank、frequency、voltage
class 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; }
};

int main()
{
	CPU a(P6, 300, 2.8);
	a.Run();
	a.Stop();

	return 0;
}

猜你喜欢

转载自blog.csdn.net/sketchlcy/article/details/104978883