《C++大学教程》 第9章 类的深入剖析:抛出异常 笔记(下)

0. 前言

《C++大学教程》 第9章 后续内容 笔记更一下。附部分课后题代码。

9. 类的深入剖析:抛出异常

9.11 组成:对象作为类的成员

组成一个类将其他类的对象作为其成员

当创建对象时,构造函数自动被调用

这一节将介绍构造函数如何通过成员初始化器来完成将参数传递给成员对象构造函数的任务

成员对象以在类的定义中声明的顺序且在包含它们的对象构造之前建立。

Employee 构造函数初始化列表

// constructor uses member initializer list to pass initializer 
// values to constructors of member objects  
Employee::Employee( const string &first, const string &last,
   const Date &dateOfBirth, const Date &dateOfHire )
   : firstName( first ), // initialize firstName
     lastName( last ), // initialize lastName
     birthDate( dateOfBirth ), // initialize birthDate
     hireDate( dateOfHire ) // initialize hireDate
{
   // output Employee object to show when constructor is called
   cout << "Employee object constructor: " 
      << firstName << ' ' << lastName << endl;
} // end Employee constructor

在头部中的冒号将参数列表和成员初始化器隔开

参数firstlast分别传送给对象的firstNamelastName,参数dateOfBirth被传递给birthDate对象的构造函数,参数dateOfHire被传递给hireDate对象的构造函数

Date 类的默认复制构造函数

Employee 类构造函数中的成员初始化器列表是怎样通过将Date对象的参数传递给Date构造函数来初始化对象birthDatehireDate呢?
编译器提供给每个类一个默认的复制构造函数,该函数将构造函数的参数对象的每个成员复制给将要初始化的对象的相应成员。

测试Date 类和Employee 类

// Fig. 9.21: fig9_21.cpp
// Demonstrating composition--an object with member objects.
#include <iostream>
#include "Employee.h" // Employee class definition
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();
} // end main
程序输出
Date object constructor for date 7/24/1949
Date object constructor for date 3/12/1988
Employee object constructor: Bob Blue

Blue, Bob  Hired: 3/12/1988  Birthday: 7/24/1949
Employee object destructor: Blue, Bob
Date object destructor for date 3/12/1988
Date object destructor for date 7/24/1949
Date object destructor for date 3/12/1988
Date object destructor for date 7/24/1949

注意:
第12行Employee manager( "Bob", "Blue", birth, hire );造成另外两次Date构造函数的调用,它们没有在程序的输出中留下痕迹。
EmployeeDate成员对象在Employee 构造函数的成员初始化器列表中初始化时,Date类默认的复制构造函数被调用。
该构造函数由编译器隐式地定义,并且不包含任何在被其调用时要显示的输出语句。

注意:输出的最后四行
倒数第4行、第3行分别显示Employee的成员对象hireDatebirthDate析构函数。
最后两行分别是运行于Date对象hirebirthDate析构函数的输出。

如果不使用成员初始列表,会发生什么

成员对象不需要显式地通过成员初始化器进行初始化。如果没提供成员初始化器,成员对象地默认构造函数将被隐式调用。

请使用成员初始化器显式地初始化成员对象。

9.12 friend函数和friend类

类的friend函数(友元函数)在类的定义域之外定义,却具有访问类的非public(以及public)成员的权限。单独的函数、整个类或其他类的成员函数都可以被声明为另一个类的友元。

friend的声明

在类定义中函数原型前加保留字friend,就将函数声明为该类的友元。要将类ClassTwo的所有成员函数声明为ClassOne类的友元,应在ClassOne定义中加入如下形式的一条声明:

friend class ClassTwo;

友元关系是授予的而不是索取的。另外,友元关系既不是对称的也不是传递的。

使用friend函数修改类的private数据

请注意,友元声明首先(按照惯例)出现在类的定义中,甚至出现在public成员函数声明之前。再次说明,友元声明可以出现在类的任何地方。

// Fig. 9.22: fig09_22.cpp  
// Friends can access private members of a class.
#include <iostream>
using namespace std;

// Count class definition 
class Count 
{
   friend void setX( Count &, int ); // friend declaration
public:
   // constructor
   Count() 
      : x( 0 ) // initialize x to 0
   { 
      // empty body
   } // end constructor Count

   // output x 
   void print() const       
   { 
      cout << x << endl; 
   } // end function print
private:
   int x; // data member
}; // end class Count

// function setX can modify private data of Count 
// because setX is declared as a friend of Count (line 9)
void setX( Count &c, int val )
{
   c.x = val; // allowed because setX is a friend of Count
} // end function setX

int main()
{
   Count counter; // create Count object

   cout << "counter.x after instantiation: ";
   counter.print();

   setX( counter, 8 ); // set x using a friend function
   cout << "counter.x after call to setX friend function: ";
   counter.print();
} // end main

重载友元函数

可以指定重载函数为类的友元。每个打算成为友元的重载函数必须在类的定义里显式地声明为类的一个友元。

友元不是成员函数

在类的定义体之内把所有的友元关系声明放在最前面的位置,并且不要在前面添加任何成员访问说明符。

9.13 使用this指针

每个对象都可以使用一个称为this(一个C++保留字)的指针来访问自己的地址。对象的this指针不是对象本身的一部分,也就是this指针占用的内存大小不会反映在对对象进行sizeof运算得到的结果中。相反,this指针作为一个隐式的参数(被编译器)传递给对象的每个非static成员函数。

使用this指针来避免名字冲突

对象隐式地使用this指针或者显式地使用this指针来引用它们的数据成员和成员函数。一个常用的this指针的explicit应用是用来避免类数据成员和成员函数参数(或其他本地变量)之间的名字冲突

为了确保代码的简洁和可维护性,以避免错误,不要让本地变量名称隐藏了数据成员。

this指针的类型

this指针的类型取决于对象的类型及使用this的成员函数是否被声明为const
例如,在Employee 类的非const成员函数中,this指针具有的类型是Employee * const(一个指向非const Employee对象的const指针)。可是在Employee 类的const成员函数中,this指针具有的类型却为const Employee * const(一个指向const Employee对象的const指针)。

隐式和显式使用this指针来访问对象的数据成员

// Fig. 9.23: fig09_23.cpp  
// Using the this pointer to refer to object members.
#include <iostream>
using namespace std;

class Test 
{
public:
   explicit Test( int = 0 ); // default constructor
   void print() const;
private:
   int x;
}; // end class Test

// constructor
Test::Test( int value ) 
   : x( value ) // initialize x to value
{ 
   // empty body 
} // end constructor Test

// print x using implicit and explicit this pointers;
// the parentheses around *this are required
void Test::print() const   
{
   // implicitly use the this pointer to access the member x
   cout << "        x = " << x;

   // explicitly use the this pointer and the arrow operator
   // to access the member x
   cout << "\n  this->x = " << this->x;

   // explicitly use the dereferenced this pointer and 
   // the dot operator to access the member x
   cout << "\n(*this).x = " << ( *this ).x << endl;
} // end function print

int main()
{
   Test testObject( 12 ); // instantiate and initialize testObject

   testObject.print();
} // end main

this指针的一个有趣用法是防止对象进行自我复制。

使用this指针使串联的函数调用成为可能

使每个函数都返回对对象的引用,以便进行串联的成员函数调用。
每个成员函数都在函数体的最后一句语句返回*this,返回类型是Time &

// set hour value
Time &Time::setHour( int h ) // note Time & return
{
   if ( h >= 0 && h < 24 )
      hour = h;
   else
      throw invalid_argument( "hour must be 0-23" );

   return *this; // enables cascading
} // end function setHour

// Fig. 9.26: fig09_26.cpp
// Cascading member-function calls with the this pointer.
#include <iostream>
#include "Time.h" // Time class definition
using namespace std;

int main()
{
   Time t; // create Time object

   // cascaded function calls
   t.setHour( 18 ).setMinute( 30 ).setSecond( 22 );

   // output time in universal and standard formats
   cout << "Universal time: ";
   t.printUniversal();

   cout << "\nStandard time: ";
   t.printStandard();

   cout << "\n\nNew standard time: ";

   // cascaded function calls
   t.setTime( 20, 20, 20 ).printStandard();
   cout << endl;
} // end main

9.14 static 类成员

static成员的声明由关键字static开头,仅有变量的一份副本供类的所有对象共享。

使用类范围数据的动机

使用static数据成员可以节省存储空间。

静态数据成员的作用域和初始化

static数据成员只在类的作用域起作用。
static数据成员可以被声明为publicprivate或者protected的。

静态数据成员必须被精确地初始化一次。基本类型的static数据成员默认情况下初始化为0。

注意类类型的static数据成员(即static成员对象),如果这个类类型具有默认构造函数,那么这样的数据成员无须初始化,因为它们的默认构造函数将会被调用。

访问静态数据成员

类的privateprotectedstatic成员通常通过类的public成员函数或类的友元访问。

即使在没有任何类的对象存在时,类的static成员仍然存在。当没有类的对象存在时,要访问类的public static成员,只需简单地在此数据成员名前加类名和二元作用域分辨运算符(::)即可。

当没有类的对象存在而要访问privateprotectedstatic类成员时,应提供public static成员函数,并通过在函数名前加类名和二元作用域分辨运算符的方式来调用此类函。每个static成员函数都是类的一项服务。

即使不存在已实例化的类的对象,类的static数据成员和static成员函数仍存在并且可以使用。

静态数据成员的说明

当将static保留字应用到文件作用域中的某个元素时,该元素只在该文件中是已知的。而类的static成员需要被任何访问文件的客户代码使用,所以不能在.cpp文件中将它们声明为static,而只在.h文件里将它们声明为static

如果成员函数不访问类的非static的数据成员或者非static的成员函数,那么它应当声明为static。与非static成员函数不同的是,static成员函数不具有this指针,因为static数据成员和static成员函数独立于类的任何对象而存在。this指针必须指向类的具体的对象,当static成员函数调用时,内存中也许没有类的任何对象存在。

static成员函数声明为const是一个编译错误。const限定符指示函数不能修改它操作的对象的内容,但是static成员函数独立于类的任何对象存在并且进行操作。

练习题

9.4 增强的Time类

time函数和localtime函数使用方法见参考链接1.

构造函数如下,其他同书中9.2节源代码:

Time::Time()
{	
	struct tm t;    //tm结构指针,存储时分秒
	time_t now;		//声明time_t类型变量
	time(&now);      //获取系统日期和时间
	localtime_s(&t, &now);   //获取当地日期和时间
	
	hour = t.tm_hour;
	minute = t.tm_min; 
	second = t.tm_sec;
	
	setTime(hour, minute, second); // validate and set time
} // end Time constructor

9.6 有理数类

减法与除法为了节省时间,并未编写。
约分函数reduction这一部分,比较有趣。

Rational.h

#ifndef RATIONAL_H
#define RATIONAL_H

class Rational
{
public:
	Rational(int = 0,int = 1);
	Rational addition(const Rational &);
	//Rational subtraction(const Rational &);
	Rational multiplication(const Rational &);
	//Rational division(const Rational &);
	void printRational();
	void printRationalAsDouble();

private:
	int numerator;
	int denominator;
	void reduction();

};

#endif RATIONAL_H

Rational.cpp

#include<iostream>
#include"Rational.h"

using namespace std;


Rational::Rational(int n,int d)
{
	numerator = n;
	denominator = d;
	reduction();
}
Rational Rational::addition(const Rational &a)
{
	Rational t;
	t.numerator = denominator * a.numerator;
	t.numerator += numerator * a.denominator;
	t.denominator = denominator * a.denominator;
	t.reduction();
	return t;
}

Rational Rational::multiplication(const Rational &b)
{
	Rational t;
	t.denominator = denominator * b.denominator;
	t.numerator = numerator * b.numerator;
	t.reduction();
	return t;
}

void Rational::printRational()
{
	if (numerator == 0)
		cout << 0;
	else if (denominator == 0)
		cout << "\nDIVIDE BY ZERO ERROR!!!\n\n" ;
	else
		cout << numerator<<'/'<<denominator ;
}

void Rational::printRationalAsDouble()
{
	cout << static_cast<double>(numerator) / denominator;
}
	
void Rational::reduction()
{
	int largest;
	largest = numerator > denominator ? numerator : denominator;
	int gcd = 0;
	for (int loop = 2 ; loop <= largest ; loop++)
	{
		if (numerator%loop == 0 && denominator%loop == 0)
		{
			gcd = loop;
		}
	}
	if (gcd != 0)
	{
		numerator /= gcd;
		denominator /= gcd;
	}
}

test.cpp

#include<iostream>
#include"Rational.h"

using namespace std;

int main()
{
	Rational c(2, 6), d(7, 8), x,y(1,0);
	x.printRational();
	y.printRational();

	c.printRational(); // prints rational object c
	cout << " + ";
	d.printRational(); // prints rational object d				
	x = c.addition(d); // adds object c and d; sets the value to x
	cout << " = ";
	x.printRational(); // prints rational object x
	cout << '\n';
	x.printRational(); // prints rational object x    
	cout << " = ";
	x.printRationalAsDouble(); // prints rational object x as double
	cout << "\n\n";
	
	c.printRational(); // prints rational object c
	cout << " x ";
	d.printRational(); // prints rational object d
	x = c.multiplication(d); // multiplies object c and d
	cout << " = ";
	x.printRational(); // prints rational object x
	cout << '\n';
	x.printRational(); // prints rational object x
	cout << " = ";
	x.printRationalAsDouble(); // prints rational object x as double
	cout << "\n\n";

}

运行结果

0
DIVIDE BY ZERO ERROR!!!

1/3 + 7/8 = 29/24
29/24 = 1.20833

1/3 x 7/8 = 7/24
7/24 = 0.291667

9.7 增强的Time类

tick成员函数

修改setHour等设置函数的代码,else部分置0,防止自增时发生异常。

void Time::setHour( int h )
{
   if ( h >= 0 && h < 24 )
      hour = h;
   else
      //throw invalid_argument( "hour must be 0-23" );
	   hour = 0;
} // end function setHour
...
void Time::tick()
{
	setSecond(getSecond()+1);
	if (getSecond() == 0)
	{
		setMinute(getMinute() + 1);
		if (getMinute() == 0)
		{
			setHour(getHour() + 1);
			if (getHour() == 0)
			{
				//setHour(getHour() + 1);
			}
		}
	}
}

test.cpp

#include <iostream>
#include <stdexcept>
#include "Time.h" // include definition of class Time from Time.h

using namespace std;

const int MAX_TICKS = 15;

int main()
{
   
   Time t1( 23, 59, 50 ); // hour, minute and second specified
   //cout << "\n\nt1: hour, minute and second specified\n  ";
   //t1.printUniversal();
   for(int ticks=1;ticks<=MAX_TICKS;ticks++)
   { 
	   cout << "\n  ";
	   //t1.printUniversal();
	   t1.printStandard();
	   t1.tick();
   }
 
} // end main

运行结果

  11:59:50 PM
  11:59:51 PM
  11:59:52 PM
  11:59:53 PM
  11:59:54 PM
  11:59:55 PM
  11:59:56 PM
  11:59:57 PM
  11:59:58 PM
  11:59:59 PM
  12:00:00 AM
  12:00:01 AM
  12:00:02 AM
  12:00:03 AM
  12:00:04 AM

9.21 IntegerSet类

遇到的部分问题见参考链接2,3和4.
“只有常量引用才能绑定到临时变量上”

IntegerSet.h

#ifndef INTEGERSET_H
#define INTEGERSET_H

#include<vector>

class IntegerSet
{
public:
	IntegerSet(const std::vector<bool> & = std::vector<bool>(5));        
	IntegerSet(const int values[], const int numberOfElements);          
	IntegerSet unionOfSets(const IntegerSet &);					       
	void insertElement(const int );                                    
	void isEqual(const IntegerSet &);                                 
	void printSet();                                                   
	void printSetTure();											   

private:
	std::vector<bool> numbers;                                         
};

#endif INTEGERSET_H

IntegerSet.cpp

#include<iostream>
#include<vector>
#include"IntegerSet.h"

using namespace std;

IntegerSet::IntegerSet(const vector <bool>  &numberTest)
	:numbers(numberTest)
{
	
}
IntegerSet::IntegerSet(const int values[], const int numberOfElements)	
{
	for (int i = 0; i < numberOfElements; i++)
	{
		numbers.push_back(values[i]);
	}
}

IntegerSet IntegerSet::unionOfSets(const IntegerSet &a)
{
	IntegerSet t;
	for (int i = 0; i < 5; i++)
	{	
		t.numbers[i] = numbers[i] | a.numbers[i];
	}
	return t;
}

void IntegerSet::insertElement(const int b)
{
	numbers[b-1] = 1;	
}

void IntegerSet::isEqual(const IntegerSet & c)
{
	if (numbers == c.numbers)
	{
		cout << "Equal\n";		
	}
	else 
		cout << "Not equal\n";
}


void IntegerSet::printSet()
{
	for (int item : numbers)
	{
		cout <<boolalpha << item<< ' ';
	}
	cout << '\n';
}
void IntegerSet::printSetTure()
{
	if (numbers != vector<bool>(5))
	{
		for (int i = 0; i < 5; i++)
		{
			if (numbers[i] != 0)
				cout << i + 1 << ' ';
		}
	}
	else
			cout << "---";
	cout << '\n';
}

test.cpp

#include<iostream>
#include<vector>
#include"IntegerSet.h"

using namespace std;

int main()
{
	const vector <bool> numbers1{ 1, 1, 0, 0, 1 };
	const vector <bool> numbers2{ 1, 0, 1, 1, 0 };
	const vector <bool> numbers3(5);
	const int values[5] = { 1, 0, 0, 1, 1 };


	cout << "Num 1 is created" << endl;
	IntegerSet num1(numbers1);
	num1.printSet();
	cout << "Num 1 stores:" << endl;
	num1.printSetTure();

	cout << "\nNum 2 is created" << endl;
	IntegerSet num2(numbers2);
	num2.printSet();
	cout << "Num 2 stores:" << endl;
	num2.printSetTure();

	IntegerSet resultTest;
	resultTest = num2.unionOfSets(num1);
	cout << "\nUnion of num 1 and num 2 is created" << endl;
	resultTest.printSet();
	cout << "Union of num 1 and num 2 stores:" << endl;
	resultTest.printSetTure();

	num2.insertElement(5);
	cout << "\nThe modified num 2" << endl;
	num2.printSet();
	cout << "The modified num 2 stores:" << endl;
	num2.printSetTure();

	cout << "\nNum 3 is created" << endl;
	IntegerSet num3(numbers3);
	num3.printSet();
	cout << "Num 3 stores:" << endl;
	num3.printSetTure();

	cout << "\nNum 4 is created" << endl;
	IntegerSet num4(numbers1);
	num4.printSet();
	cout << "Num 4 stores:" << endl;
	num4.printSetTure();

	cout << "\nNum 1 and num 2 are:" << endl;
	num1.isEqual(num2);
	cout << "\nNum 1 and num 4 are:" << endl;
	num1.isEqual(num4);

	IntegerSet num5(values, 5);
	cout << "\nNum 5 is created" << endl;
	num5.printSet();
	cout << "Num 5 stores:" << endl;
	num5.printSetTure();
}

运行结果

Num 1 is created
1 1 0 0 1
Num 1 stores:
1 2 5

Num 2 is created
1 0 1 1 0
Num 2 stores:
1 3 4

Union of num 1 and num 2 is created
1 1 1 1 1
Union of num 1 and num 2 stores:
1 2 3 4 5

The modified num 2
1 0 1 1 1
The modified num 2 stores:
1 3 4 5

Num 3 is created
0 0 0 0 0
Num 3 stores:
---

Num 4 is created
1 1 0 0 1
Num 4 stores:
1 2 5

Num 1 and num 2 are:
Not equal

Num 1 and num 4 are:
Equal

Num 5 is created
1 0 0 1 1
Num 5 stores:
1 4 5

结语

第9章已全部更完。

感谢苏舍长深夜答疑解惑,感激ing~

个人水平有限,有问题欢迎各位大神批评指正!

参考链接

  1. c++中利用localtime_s函数格式化输出当地日期与时间
    https://www.cnblogs.com/curo0119/p/8893036.html

  2. C/C++笔试系列--如何利用成员变量作为成员函数的默认参数
    https://blog.csdn.net/sailor_8318/article/details/3348360?utm_source=blogxgwz7

  3. 菜鸟学C++小结(6)-------vector使用
    https://www.eefocus.com/yw0520205036/blog/12-04/246752_47297.html

  4. VS2010 Debug模式下运行时出现 vector iterator not dereferencable
    https://bbs.csdn.net/topics/390066840

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值