文章目录
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
在头部中的冒号将参数列表和成员初始化器隔开
参数first
、last
分别传送给对象的firstName
、lastName
,参数dateOfBirth
被传递给birthDate
对象的构造函数,参数dateOfHire
被传递给hireDate
对象的构造函数
Date 类的默认复制构造函数
Employee 类构造函数中的成员初始化器列表是怎样通过将Date对象的参数传递给Date构造函数来初始化对象birthDate
和hireDate
呢?
编译器提供给每个类一个默认的复制构造函数,该函数将构造函数的参数对象的每个成员复制给将要初始化的对象的相应成员。
测试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
构造函数的调用,它们没有在程序的输出中留下痕迹。
当Employee
的Date
成员对象在Employee
构造函数的成员初始化器列表中初始化时,Date
类默认的复制构造函数被调用。
该构造函数由编译器隐式地定义,并且不包含任何在被其调用时要显示的输出语句。
注意:输出的最后四行
倒数第4行、第3行分别显示Employee
的成员对象hireDate
和birthDate
析构函数。
最后两行分别是运行于Date
对象hire
和birth
的Date
析构函数的输出。
如果不使用成员初始列表,会发生什么
成员对象不需要显式地通过成员初始化器进行初始化。如果没提供成员初始化器,成员对象地默认构造函数将被隐式调用。
请使用成员初始化器显式地初始化成员对象。
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
数据成员可以被声明为public
、private
或者protected
的。
静态数据成员必须被精确地初始化一次。基本类型的static
数据成员默认情况下初始化为0。
注意类类型的static
数据成员(即static
成员对象),如果这个类类型具有默认构造函数,那么这样的数据成员无须初始化,因为它们的默认构造函数将会被调用。
访问静态数据成员
类的private
和protected
的static
成员通常通过类的public
成员函数或类的友元访问。
即使在没有任何类的对象存在时,类的static
成员仍然存在。当没有类的对象存在时,要访问类的public static
成员,只需简单地在此数据成员名前加类名和二元作用域分辨运算符(::
)即可。
当没有类的对象存在而要访问private
或protected
的static
类成员时,应提供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~
个人水平有限,有问题欢迎各位大神批评指正!
参考链接
-
c++中利用localtime_s函数格式化输出当地日期与时间
https://www.cnblogs.com/curo0119/p/8893036.html -
C/C++笔试系列--如何利用成员变量作为成员函数的默认参数
https://blog.youkuaiyun.com/sailor_8318/article/details/3348360?utm_source=blogxgwz7 -
菜鸟学C++小结(6)-------vector使用
https://www.eefocus.com/yw0520205036/blog/12-04/246752_47297.html -
VS2010 Debug模式下运行时出现 vector iterator not dereferencable
https://bbs.youkuaiyun.com/topics/390066840