《C++大学教程》学习笔记(九)

本文深入探讨C++中类的设计与实现,包括Time类的实例研究、异常处理、对象作为成员的应用等内容,同时介绍了this指针、静态成员等高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《C++大学教程》学习笔记(九)

1.Time类实例研究

1.1包含防护

在开始之前,先说明一个重要的C++软件工程概念:在头文件中使用“包含防护”,从而避免头文件中的代码被多次包含到同一源代码文件中的情况。

通过在头文件中加入预处理指令#ifndef,#define,#endif即可构成“包含防护”。同时,在预处理指令 #ifndef 和 #define 中,应使用大写的头文件名,并用下划线代替圆点。

具体使用方法如下所示:

//prevent multiple inclusions of header
#ifndef TIME_H
#define TIME_H
    ...
#endif

1.2Time类的实例

创建一个Time类,实现对时间(HH:MM:SS)的基本操作。Time实例中具体实践了“包含防护”,异常处理这些内容,下面给出文件结构及代码:
这里写图片描述

Time.h文件:

//包含防护
#ifndef Time_h
#define Time_h

class Time{
public:
    Time();
    void setTime(int,int,int);
    void printUniversal() const;    //输出格林威治时间
    void printStandard() const;     //输出标准时间

private:
    unsigned int hour;
    unsigned int minute;
    unsigned int second;
};

#endif /* Time_h */

Time.cpp文件:

#include "Time.h"
#include <iostream>
#include <iomanip>
#include <stdexcept>
using namespace std;

Time::Time()
: hour(0),minute(0),second(0)
{
    //Nothing
}

void Time::setTime(int h, int m, int s){

    if ((h >= 0 && h <=24) && (m >= 0 && m <= 60) && (s >= 0 && s <= 60)) {
        hour = h;
        minute = m;
        second = s;
    }
    else
        throw invalid_argument("Wrong Input!\n");

}

void Time::printUniversal() const {
    cout << setfill('0') << setw(2) << hour << ":"
    << setw(2) << minute << ":"
    << setw(2) << second << endl;
}

void Time::printStandard() const {
    cout << setfill('0') << setw(2) << ((hour == 0 || hour == 12) ? 12 : hour % 12) << ":"
    << setw(2) << minute << ":"
    << setw(2) << second << (hour < 12 ? " AM" : " PM") << endl;
}

main.cpp文件:

#include <iostream>
#include <stdexcept>
#include "Time.h"
using namespace std;

int main(){

    Time t;

    cout << "The initial universal time is: ";
    t.printUniversal();
    cout << "The initial standard time is: ";
    t.printStandard();

    t.setTime(13, 27, 6);

    cout << "\nThe universal time after setTime is: ";
    t.printUniversal();
    cout << "The tandard time after setTime is: ";
    t.printStandard();

    try {
        t.setTime(99, 99, -100);  
    } catch (invalid_argument e) {
        cout << "\nException: " << e.what();      //注意e.what()的what函数是属于logic_error命名空间的
    }

    return 0;
}

运行结果如下图所示:
这里写图片描述

1.3通过对象、引用、指针访问类的public成员

以下三种方式调用成员函数的方式不同,但归根到底都只是对同一个Time对象(即 t )进行操作。

    Time t;
    Time &tRef = t;
    Time *tPtr = &t;

    t.setTime(0, 0, 0);
    tRef.setTime(1, 1, 1);
    tPtr->setTime(2, 2, 2);

2.改进型Time类:具有默认实参的构造函数

在原有的Time类上加以改进,引入了具有默认实参的构造函数,优化了setTime函数,为每个private数据成员搭配了相应的get、set函数。
具体代码如下所示:

Time.h文件:

//包含防护
#ifndef Time_h
#define Time_h

class Time{
public:
    explicit Time(int = 0,int = 0,int = 0);

    void setTime(int,int,int);
    void setHour(int);
    void setMinute(int);
    void setSecond(int);

    unsigned int getHour() const;
    unsigned int getMinute() const;
    unsigned int getSecond() const;

    void printUniversal() const;    //输出格林威治时间
    void printStandard() const;     //输出标准时间

private:
    unsigned int hour;         // 0 - 23
    unsigned int minute;       // 0 - 59
    unsigned int second;       // 0 - 59
};

#endif /* Time_h */

Time.cpp文件:

#include "Time.h"
#include <iostream>
#include <iomanip>
#include <stdexcept>
using namespace std;

Time::Time(int hour,int minute,int second){
    setTime(hour, minute, second);
}

void Time::setTime(int h, int m, int s){
    setHour(h);
    setMinute(m);
    setSecond(s);
}

void Time::setHour(int h){
    if (h >= 0 && h < 24)
        hour = h;
    else
        throw  invalid_argument("hour must be 0-23");
}

void Time::setMinute(int m){
    if (m >= 0 && m < 60)
        minute = m;
    else
        throw  invalid_argument("minute must be 0-59");
}

void Time::setSecond(int s){
    if (s >= 0 && s < 60)
        second = s;
    else
        throw  invalid_argument("second must be 0-59");
}

unsigned int Time::getHour() const{
    return hour;
}

unsigned int Time::getMinute() const{
    return minute;
}

unsigned int Time::getSecond() const{
    return second;
}

void Time::printUniversal() const {
    cout << setfill('0') << setw(2) << hour << ":"
    << setw(2) << minute << ":"
    << setw(2) << second << endl;
}

void Time::printStandard() const {
    cout << setfill('0') << setw(2) << ((hour == 0 || hour == 12) ? 12 : hour % 12) << ":"
    << setw(2) << minute << ":"
    << setw(2) << second << (hour < 12 ? " AM" : " PM") << endl;
}

main.cpp文件:

#include <iostream>
#include <stdexcept>
#include "Time.h"
using namespace std;

int main(){

    Time t1;
    Time t2(2);
    Time t3(21,34);
    Time t4(12,25,42);

    cout << "t1:\n";
    t1.printUniversal();
    t1.printStandard();

    cout << "\nt2:\n";
    t2.printUniversal();
    t2.printStandard();

    cout << "\nt3:\n";
    t3.printUniversal();
    t3.printStandard();

    cout << "\nt4:\n";
    t4.printUniversal();
    t4.printStandard();

    try {
        Time t5(23,61,59);
    } catch (invalid_argument e) {
        cout << "\nException: " << e.what() << endl;        //注意e.what()的what函数是属于logic_error命名空间的
    }

    return 0;
}

运行结果如下图所示:
这里写图片描述

3.零零散散

析构函数(destructor)是一种特殊的成员函数,它不接收任何参数,也不返回任何值。当对象撤销时,类的析构函数会隐式地调用。析构函数的声明方式如下所示:

class Employee{

public:
    Employee(const string&,const string&,const Date&,const Date&);
    ~Employee();

private:
    ...
    ...    
};

返回一个private类型的数据成员的引用或指针会破坏类的封装性,可以参考书P299。

一般情况下,类的对象是可以相互赋值的,比如:

Date date1(7,4,2004);
Date date2;

date2 = date1;

对于每个类,编译器都提供一个默认的复制构造函数,可以将原始对象(date1)的每个成员复制到新对象(date2)的相应成员中。
但这种复制并不是总是有效的,当所用类的数据成员包含指向动态分配内存的指针时,默认复制构造函数会引发严重问题,这种时候就需要自己去定义一个定制的复制构造函数。

一个“const”对象的“常量性”是从构造函数完成对象的初始化到析构函数被调用之间,因此将构造函数和析构函数声明为const是一个编译错误。
const对象的使用是非常严格的:即const对象只能访问被显式声明为const的成员函数,无论这个函数是否修改了这个对象。

类的friend函数(友元函数)在类的作用域之外定义,却具有访问类的public、private、protected成员的权限。友元定义可以放在类定义的任何地方,即使friend函数的原型在类定义内出现,友元仍不是成员函数
友元关系是授予的而不是索取的,既不是对称的也不是传递的,声明一个友元函数如下所示:

class Court{

friend void setX(Count & , int);        //友元函数定义

public:
        ...
        ...
private:
        int x;
};

4.组成:对象作为类的成员

软件复用性的一个普遍的形式是组成,即一个类将其他类的对象作为成员。
在下面这个实例中,Employee类中将Date类的两个对象作为成员,具体的文件结构与代码如下所示:
这里写图片描述

Date.h文件:

#ifndef Date_h
#define Date_h

class Date{

public:
    static const unsigned int monthsPerYear = 12;
    explicit Date (int = 1,int = 1,int = 1);
    void print() const;
    ~Date();

private:
    unsigned int month;     //1 - 12
    unsigned int day;       //1 - 31 based on month
    unsigned int year;

    unsigned int checkDay(int) const;
};

#endif /* Date_h */

Date.cpp文件:

#include "Date.h"
#include <iostream>
#include <array>
#include <stdexcept>
using namespace std;

Date::Date(int m,int d,int y){

    if (m > 0 && m <= monthsPerYear)
        month = m;
    else
        throw invalid_argument("month must be 1 - 12\n");

    year = y;
    day = checkDay(d);

    cout << "Date object constructor for date: ";
    print();
    cout << endl;

}

Date::~Date(){
    cout << "Date object destructor for date: ";
    print();
    cout << endl;
}

void Date::print() const {
    cout << month << "/" << day << "/" << year;
}

unsigned int Date::checkDay(int testDay) const{
    static const array< int , monthsPerYear + 1 > daysPerMonth =
    {0,31,28,31,30,31,30,31,31,30,31,30,31};

    if (testDay > 0 && testDay <= daysPerMonth[month])
        return testDay;

    if (testDay == 29 && month == 2 && (year % 400 == 0 || (year % 4 == 0 && year%100 != 0)))
        return testDay;

    throw invalid_argument("Invalid day for current month and year\n");
}

Employee.h文件:

#ifndef Employee_h
#define Employee_h

#include "Date.h"
#include <string>
using namespace std;

class Employee{

public:
    Employee(const string&,const string&,const Date&,const Date&);
    ~Employee();
    void print() const;

private:
    string firstName;
    string lastName;
    const Date birthDate;
    const Date hireDate;
    //当Employee对象撤销时,这两个Date也要撤销,即执行析构函数
};

#endif /* Employee_h */

Employee.cpp文件:

#include "Employee.h"
#include "Date.h"
#include <iostream>
using namespace std;

Employee::Employee(const string &first,const string &last,const Date &birth,const Date &hire)
:firstName(first),lastName(last),birthDate(birth),hireDate(hire)
{
    cout << "Employee object constructor: " << firstName << " " << lastName << endl;
}

void Employee::print()const {
    cout << lastName << ", " << firstName << " Birthday: ";
    birthDate.print();
    cout << " Hired: ";
    hireDate.print();
    cout << endl;
}

Employee::~Employee(){
    cout << "Employee object destructor: " << firstName << " " << lastName << endl;
}

main.cpp文件:

#include "Date.h"
#include "Employee.h"
#include <iostream>
using namespace std;

int main(){

    Date birth(7,24,1949);
    Date hire(3,12,1988);
    Employee manager("Bob","blue",birth,hire);

    cout << endl;
    manager.print();
    cout << endl;

    return 0;

}

上述程序的运行结果是:
这里写图片描述

5.this指针

每个对象都可以用一个称为this的指针来访问自己的地址,通过使用this指针可以避免命名冲突。比如:

//Time类中的setHour成员函数
void Time::setHour(int hour){
    if (hour >= 0 && hour < 24)
        this->hour = hour;
        //等号左边是数据成员hour,等号右边是局部变量hour
    else
        throw  invalid_argument("hour must be 0-23");
}

除了

this->hour 

这种访问方式之外,

(* this).hour

也是可以的。

除此之外,使用this指针还能使串联的函数调用成为可能,具体的使用方法可以见书P310。

6.static成员与static成员函数

我们考虑这么一种情况:在Employee类引入一个普通的count变量来记录实际雇员的个数,而Employee类又会产生多个实例对象。当一名雇员加入或离职时,必须对所有对象的count值都进行修改(以保持统一),这显然是不必要的。
事实上,我们只需要一个static的count变量,就能很好的表示这一被所有实例所共享的性质。
由于static成员在对象撤销之后仍然存在,所以要想使用static成员,就必须搭配相应的static成员函数。
我们以Employee类来简单说明:

#ifndef Employee_h
#define Employee_h

class Employee{

public:
    ...
    ...
    static unsigned int getCount();     //作为static数据成员搭配使用的static成员函数

private:
   ...
   ...
    static unsigned int count;           //无法在此初始化,需要到.cpp文件中初始化
};

#endif /* Employee_h */

getCount函数的具体实现则是:

unsigned int Employee::getCount(){
    return count;
}

count的初始化(.cpp文件中)则是:

unsigned int Employee::count = 0;

在main函数中,当没有一个Employee对象时,通过

Employee::getCount()

可以访问到count的值。

而当存在Employ对象时,则通过

Employee e1("a","b");

cout << "Employee number after: " << e1.getCount() << endl;
cout << "Employee number after: " << Employee::getCount() << endl << endl;

两种方式均可访问。

C++大学教程(目录) 第1章 计算机与C++编程简介-------------------------------------------------1 1.1 简介--------------------------------------------------------------1 1.2 什么是计算机------------------------------------------------------3 1.3 计算机组成--------------------------------------------------------3 1.4 操作系统的变革----------------------------------------------------4 1.5 个人计算、分布式计算与客户/服务器计算-----------------------------4 1.6 机器语言、汇编语言和高级语言--------------------------------------5 1.7 C语言与C++的历史--------------------------------------------------6 1.8 C++标准库---------------------------------------------------------7 1.9 Java、Internet与万维网--------------------------------------------7 1.10 其它高级语言------------------------------------------------------8 1.11 结构化编程--------------------------------------------------------8 1.12 典型C++环境基础---------------------------------------------------8 1.13 C++与本书的一般说明----------------------------------------------10 1.14 C++编程简介------------------------------------------------------11 1.15 简单程序:打印一行文本-------------------------------------------12 1.16 简单程序:两个整数相加-------------------------------------------15 1.17 内存的概念-------------------------------------------------------18 1.18 算术运算---------------------------------------------------------19 1.19 判断:相等与关系运算---------------------------------------------21 1.20 新型头文件与名字空间---------------------------------------------24 1.21 有关对象的思考---------------------------------------------------25 小结------------------------------------------------------------------27 术语------------------------------------------------------------------29 自测练习--------------------------------------------------------------31 自测练习答案----------------------------------------------------------33 练习------------------------------------------------------------------34 第2章 控制结构-----------------------------------------------------------38 2.1 简
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值