c++ primer第五版----学习笔记(七)

1.类的基本思想:数据抽象和封装

  • 数据抽象:一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数
  • 封装实现了类的接口和实现的分离,封装后的类隐藏了它的实现细节

2.关于类

  • 定义在类内部的函数是隐式的inline函数(内联函数)
  • this:当我们调用成员函数时,实际上实在替某个对象调用它;任何对类成员的直接访问都被看作this的隐式调用
  • const成员函数(const紧跟在参数列表后面):表示this是一个常量指针
  • 如果非成员函数是类接口的一部分,则这些函数的声明应该与类在同一个头文件中

3.构造函数:初始化类对象的数据成员,类的对象被创建,则会执行构造函数

  • 合成的默认构造函数:由编译器创建的构造函数。(1)如果存在类内的初始值,用它来初始化成员;(2)否则默认初始化成员
  • 某些类不能依赖于合成的默认构造函数:Ⅰ、只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数;Ⅱ、如果类包含有内置类型或者复合类型的成员。只有当这些成员全部被赋予初始值时,才适合用合成的ⅡⅠ、某些情况下编译器不能为类提供默认的构造函数
  • =default:定义一个默认构造函数

4.访问控制与封装

  • class与struct关键字:
/*
在C++中,struct与class类似,既可以包含成员变量,又可以包含成员函数
C++中的struct与class基本是通用的,只有几个细节不同:
1:使用class时,类中的成员默认都是private属性的;而使用struct时,结构体中的成员默认都是public属性的
2.class继承默认是private继承,而struct继承默认是public继承
3.class可以使用模板,而struct不能
*/

/*
1:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,默认的成员变量访问控制是private的
2:当你觉得你要做的更像是一种数据结构时,那么用struct,如果你要做的更像是一种对象时,那么用class
3:然而对于访问控制,应该在程序中明确的指出,而不是依靠默认
*/
  • 友元:类可以允许其他类或其他函数访问它的非公有成员,则令其他类或其他函数成为它的友元
  • 一般来说,最好在类定义开始或结束前的位置集中声明友元
//定义友元使用friend关键字
class Sales_data{
//为Sales_data的非成员函数所做的友元声明
friend Sales_data add(const Sales_data&, const Sales_data&);
/*......*/
public:
    /*.....*/
private:
    /*.....*/
};
  • 封装的两个重要的优点:(1)确保用户代码不会无意间破坏封装对象的状态;(2)被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码

5.类的其他特性

  • 可变数据成员:在变量的声明中加入mutable关键字
class Screen{ 
public:
    void some_number() const;             //即使在一个const对象内也能被修改
private:
    mutable size_t access_ctr;
};
void Screen::some_number() const
{
    ++access_ctr;
}
  • 返回*this的成员函数:对于此类成员函数,如果定义的返回类型不是引用,则成员函数的返回值将是*this的副本,因此调用此类成员函数只能改变其副本,而不能更改其对象的值
  • 一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用
  • 对于一个类来说,在我们创建它的对象之前该类必须被定义过,而不能仅仅被声明
  • ~友元关系不存在传递性;~每个类负责控制自己的友元类或友元函数;~令某个类的成员函数作为自己的友元,必须声明该成员函数属于哪个类

6.名字查找与类的作用域

  • 编译器处理完类中的全部声明后才会处理成员函数的定义
  • 对类成员声明的名字查找:现在类作用域中寻找,若无匹配成员,则在类的外层作用域中寻找
  • 类型名的定义通常出现在类的开始处,且不能在类中重新定义

7.构造函数再探

  • 如果成员是const、引用、或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始值列表为这些成员提供初始值
  • 最好令构造函数初始值的顺序与成员声明的顺序保持一致,尽量避免使用某些成员初始化其他成员
class X{
    int i;
    int j;
public:
    //未定义的,i在j之前被初始化
    X(int val):j(val),i(j) {}
};
  • 委托构造函数:使用它所属类的其他构造函数执行它自己的初始化过程
  • 在实际中,如果定义了其他构造函数,那么最好提供一个默认构造函数
  • 隐式的类类型转换:
//1.转换构造函数:构造函数只接受一个实参,实际上定义了转换为此类类型的隐式转换机制
string null_book = "9-999-99999-9";
item.combine(null_book);   //string类型隐式转换为Sales_data类型

//2.只允许一步类类型转换
item.combine("9-999-99999-9");   //错误:需要两步类型转换
//显示的转换成string,隐式的转换成Sales_data
item.combine(string("9-999-99999-9"));
//隐式的转换成string,显示的转换成Sales_data
item.combine(Sales_data("9-999-99999-9"));

//3.抑制构造函数的隐式转换,将构造函数声明成explicit
// 关键字explicit只对一个实参的构造函数有效;只能在类内声明构造函数添加关键字,在类外定义时不允许添加
explicit Sales_data::Sales_data(istream &is)   //错误
{
    read(is, *this);
}
//explicit构造函数只能用于直接初始化
Sales_data item(null_book);  //直接初始化
Sales_data item = null_book;  //错误:不能用于拷贝形式的初始化

//4.为转换显示的使用构造函数:使用static_cast

8.聚合类和字面值常量类

  • 聚合类:用户可以直接访问成员、且具有独特的初始化语法形式
//聚合类的特点:
/*
1.所有成员都是public的
2.没有定义任何构造函数
3.没有类内初始值
4.没有基类,也没有virtual函数
*/
//例:
struct Data{
    int ival;
    string s;
};
  • 字面值常量类:数据成员都是字面值类型的聚合类(还有其他几个特例)
  • constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式

9.类的静态成员

  • 声明静态成员:在成员的声明之前加上关键字static;静态成员不能被声明成const的
  • 定义并初始化一个静态成员:需要指定对象的类型名、类名、作用域运算符以及成员自己的名字
  • 如果在类的内部提供了一个初始值,则在外部成员的定义则不需要指定初始值了
  • 静态成员可以是不完全类型;可以使用静态成员作为默认实参

10.部分习题解答

7.8:

read函数需要对传入的对象进行修改

7.10:

同时判断两次读操作的对象

7.13:

Sales_data total(cin);
	if (cin)
	{
		Sales_data trans(cin);
		do
		{
			if (total.isbn() == trans.isbn())
			{
				total.combine(trans);
			}
			else
			{
				print(cout, total);
				total = trans;
			}
		} while (read(cin, trans));
		print(cout, total);
	}
	else
	{
		cerr << "NO data?!" << endl;
		return -1;
	}

7.18:

 

封装实现了类的接口和实现的分离,隐藏了类的实现细节,用户只能接触到类的接口。

优点:隐藏类的实现细节;
让使用者只能通过程序规定的方法来访问数据;
可以方便的加入存取控制语句,限制不合理操作;

类自身的安全性提升,只能被访问不能被修改;

类的细节可以随时改变,不需要修改用户级别的代码;

7.20:

优点:可以灵活的实现访问类的私有成员

缺点:一个类将对非公有成员的访问权授予其他类或函数

7.25:

能,因为Screen的数据成员都是内置类型

7.28:

如使用Screen,则这些函数只返回一个临时副本,不会改变myScreen的值

7.30:

优点:(1)当需要将一个对象作为整体引用而不是引用对象的一个成员时,使用this,则该函数返回对调用该函数的对象的引用

(2)可以非常明确地指出访问的是调用该函数的对象的成员,且可以在成员函数中使用与数据成员同名的形参

缺点:不必要使用,代码多余

7.31:

class X;
class Y{
    X object;
};

class X{
    Y *my_pointer = NULL;
};

7.33:

pos在类中声明在作用域外使用时要声明属于哪个类

7.34:

出现错误,pos未定义

7.35:

Type有两种类型,应该将最后一个函数的Type声明成Exercise::作用域,因为返回值是double类型

//应添加Exercise::
Exercise::Type Exercise::setVal(Type parm)
{
    val = parm + initVal();
    return val;
}

7.38:

Sales_data(istream &is = cin) { read(is, *this); }

7.39:

不合法,如果为两个默认构造函数都赋予默认实参,则这两个构造函数都具有了默认构造函数的作用。一旦不提供任何实参地创建类的对象,则编译器无法判断这两个构造函数哪个更好,从而出现二义性错误

7.41:

理解委托构造函数:

#include <iostream>
#include <string>
using namespace std;

class Sales_data
{
	friend istream &read(istream &is, Sales_data &item);
	friend ostream &print(ostream &os, const Sales_data &item);
public:
	Sales_data(const string &book, unsigned num, double sellp, double salep) : bookNo(book), units_sold(num), sellingprice(sellp), saleprice(salep)
	{
		if (sellingprice)
			discount = saleprice / sellingprice;
		cout << "The constructor can receive booNo, units_sold, sellingprice, saleprice." << endl;
	}

	Sales_data() : Sales_data("", 0, 0, 0)
	{
		cout << "The constructor needs no information." << endl;
	}

	Sales_data(const string &book) : Sales_data(book, 0, 0, 0)
	{
		cout << "The constructor receives bookNo." << endl;
	}

	Sales_data(istream &is) : Sales_data()
	{
		read(is, *this);
		cout << "The constructor receives the information that user inputs." << endl;
	}

private:
	string bookNo;
	unsigned units_sold = 0;
	double sellingprice = 0.0;
	double saleprice = 0.0;
	double discount = 0.0;
};

istream &read(istream &is, Sales_data &item)
{
	is >> item.bookNo >> item.units_sold >> item.sellingprice >> item.saleprice;
	return is;
}

ostream &print(ostream &os, const Sales_data &item)
{
	os << item.bookNo << " " << item.units_sold << " " << item.sellingprice << " " << item.saleprice << " " << item.discount;
	return os;
}

int main()
{
	Sales_data first("978-121-15535-2", 85, 128, 109);
	Sales_data second;
	Sales_data third("978-121-15535-2");
	Sales_data last(cin);

	return 0;
}

7.43:

#include <iostream>
using namespace std;

class Nodefault
{
public:
	Nodefault(int i)
	{
		val = i;
	}
	int val;
};

class C
{
public:
	Nodefault nd;
	C(int i = 5) : nd(i) { }
};

int main()
{
	C c;
	cout << c.nd.val << endl;
	system("pause");
	return 0;
}

7.44:

不合法。vector中每个元素都是NoDefault且执行认初始化,然而在类中并未定义默认构造函数,编译器会报错

7.45:

合法,在C中定义了带参数的默认构造函数

7.47:

应该是explicit的,否则编译器就有可能自动将一个string对象转换成Sales_data对象,显得有点随意

 

7.50:

Person类有3个构造函数,前两个构造函数接收的参数个数都不是1,所以他们不存在隐式转换的问题,当然也不必指定explicit.

Person类的最后一个构造函数Person(std::istream &is);只接受一个参数,默认情况下它会把读入的数据自动转换成Person对象,可以明确指定。在其他情况下则不希望自动类型转换的发生。所以应该把这个构造函数指定为explicit的。



7.51:

string接受的单参数是const char*类型,如果我们得到了一个常量指针,则把它看做string对象是自然而然的过程,编译器自动把参数类型转换成类类型也非常符合逻辑,因此我们无须指定为explicit。

与string相反,vector接受的单参数是int类型,这个参数的原意是指定vector的容量。如果我们在本来需要vector的地方提供一个int值并且希望这个int值自动转换成vector,则这个过程显得比较牵强,因此把vector的单参数构造函数定义成explicit的更加合理。



7.52:

这个程序相对item进行聚合类初始化操作,用花括号内的值初始化item的数据成员。然而实际过程与程序的愿意不符合,编译器会出错。聚合类要满足没有类内初始值这一条件,因此去掉类内初始值,就可以正常运行了。

7.54:


这些以set_开头的成员不能声明成constexpr,这些函数的作用是设置数据成员的值,而constexpr函数只能包含return语句,不允许执行其他任务。



7.55:

因为Data类是聚合类,所以它也是一个字面值常量。



7.56:什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?

静态成员是指声明语句之前带有关键字static的类成员,静态成员不是任意单独对象的组成部分,而是由该类的全体对象所共享。

静态成员的优点包括:可以是私有成员,而全局对象不可以;通过阅读程序可以非常容易地看出静态成员与特定类关联,使得程序的含义清晰明了。

静态成员与普通成员的区别主要体现在普通成员与类的对象关联,是某个具体对象的组成部分;而静态成员从不属于任何具体的对象,它由该类的所有对象共享。静态成员可以作为默认实参,而普通数据成员不可以。

7.58:

 

在类的内部,rate和vec的初始化是错误的,因为除了静态常量成员之外,其他静态成员不能在类的内部初始化。另外,example.c文件的两条语句也是错误的,在这里必须给出静态成员的初始值。

关于Sales_data类:

//Sales_data.h

struct Sales_data
{
	//默认构造函数
	Sales_data(){}
	//以istream为参数的构造函数,调用read函数以给数据成员赋值
	Sales_data(istream &is);
	//得到书本的isbn
	string isbn() const { return bookNo; }
	//声明combine函数
	Sales_data& combine(const Sales_data& item);
	//声明avg_price函数
	double avg_price() const;
	//书本的isbn号
	string bookNo;
	//书本的销售量
	unsigned units_sold = 0;
	//书本的销售价格
	double revenue = 0.0;
};

//实现avg_price函数,得到平均价格,并定义成内联函数
inline
double Sales_data::avg_price() const
{
	if (units_sold)
	{
		return revenue / units_sold;
	}
	else
	{
		return 0;
	}
}

//实现combine函数,将两个对象的成员相加,返回Sales_data类型对象
Sales_data& Sales_data::combine(const Sales_data& item)
{
	units_sold += item.units_sold;
	revenue += item.revenue;
	return *this;
}

//输入的交易信息包括ISBN、售出总数、售出价格
istream &read(istream &is, Sales_data &item)
{
	double price = 0;
	is >> item.bookNo >> item.units_sold  >> price;
	item.revenue = item.units_sold * price;
	return is;
}

//输出交易信息包括ISBN、售出总数、售出总额和平均价格
ostream &print(ostream &os,const  Sales_data &item)
{
	os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
	return os;
}

//实现构造函数(必须定义在类的外部,因为read函数非类成员函数)
Sales_data::Sales_data(istream &is)
{
	read(is, *this);//read函数的作用是从is中读取一条交易信息然后存入this对象中
}

关于Person类:

//Person.h

class Person
{
public:
	Person(){}
	//定义构造函数,对成员初始化
	Person(string p_name, string p_address)
	{
		name = p_name;
		address = p_address;
	}
	//返回姓名
	string  getname() const{ return name; }
	//返回地址
	string  getaddress() const {  return address; }
	//定义两个公有成员,姓名、地址
	string name, address;
};

//输入的信息包括姓名及地址
istream &read(istream &is, Person &person)
{
	is >> person.name >> person.address;
	return is;
}

//输出的信息包括姓名及地址
ostream &print(ostream &os, Person &person)
{
	os << person.getname() <<" "<< person.getaddress() << endl;
	return os;
}

关于Screen类:

class Screen;
class window_mgr
{
public:
	using ScreenIndex = vector<Screen>::size_type;
	void clear(ScreenIndex);
private:
	vector<Screen> screens;
};


class Screen
{
public:
	typedef string::size_type pos;

	//默认构造函数
	Screen() = default;
	//构造函数1
	Screen(pos ht,pos wd):height(ht),width(wd),contents(ht * wd , ' '){}
	//构造函数2
	Screen(pos ht, pos wd, char c):height(ht),width(wd),contents(ht * wd,c){}
	char get() const { return contents[currsor]; }
	char get(pos r, pos c) const { return contents[r * width + c]; }
	Screen &move(pos r, pos c);
	Screen &set(char c);
	Screen &display(ostream &os);

	friend void window_mgr::clear(ScreenIndex);
private:
	pos currsor = 0;
	pos height = 0, width = 0;
	string contents;
};

inline 
Screen &Screen::move(pos r, pos c)
{
	pos row = r * width;
	currsor = row + c;
	return *this;
}

inline
Screen &Screen::set(char c)
{
	contents[currsor] = c;
	return *this;
}

inline 
Screen &Screen::display(ostream &os)
{
	os<< contents;
	return *this;
}

void window_mgr::clear(ScreenIndex i)
{
	Screen &s = screens[i];
	s.contents = string(s.height * s.width, ' ');
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值