《c++primer》第七章 ·类·知识点总结

类的基本思想是数据抽象封装
在这里插入图片描述
类要想实现数据抽象和封装,需要首先定义一个抽象数据类型,在抽象数据类型中,类的设计者负责考虑类的实现过程;类的使用者只需抽象地思考类型做了什么,而无需了解类型的工作细节。

7.1定义抽象数据类型

(一) 成员函数

1、允许用户直接访问其数据成员,并且要求由用户来编写操作的类就不是一个抽象数据类型.
比如:

struct Sales_data{
	std:: string bookNo;
	unsigned units_sdold = 0;
	double revenue = 0.0;
};

2、成员函数的声明必须在类的内部,它的定义则既可以在类的内部也可以在类的外部。
在类内部定义的成员函数是隐式的inline函数(即类内定义的成员函数被默认为内联函数)
3、非成员函数的定义和声明都在类的外部。 在这里插入图片描述

(二)this指针

1、成员函数内部可通过一个名为this的额外的隐式参数来访问调用它的那个对象
注意是对象而不是类
2、默认情况下,this指针是指向类类型非常量版本的常量指针,比如Sales_data*const
3、成员函数内部可以直接调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为this所指的正是这个对象;
4、常量成员函数:在成员函数的参数列表之后紧跟着const(见Sales_data类中的成员函数isbn( ))。这里的const,用于修改隐式的this指针,旨在把默认类型为Sales_data*const的this指针,修改为const Sales_data* const类型;
例如:

struct Sales_data{
	//成员函数
	std::string isbn() const { return bookNo;}//在成员函数内部可直接调用该类对象的数据成员,因为this所指的正是这个对象。而this是隐式参数。
	//成员变量
	std::string bookNo;
}
int main()
{
	Sales_data total;
	total.isbn();
	//编译器把对象total的地址传递给isbn的隐式参数this。
	//std::string isbn() const { return bookNo;}等价于std::string isbn() const { return this->bookNo;}
}

(三)构造函数

1、默认构造函数:无须任何形参的构造函数。如果我们的类没有显示地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数(如:Sales_data类);

2、只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数;如果已显示定义了非默认构造函数,要想调用默认构造函数,必须显示地把默认构造函数声明出来。

3、某些类不能依赖于合成的默认构造函数,具体包括:
①类包含有内置类型或者复合类型的成员。只有当这些成员全部被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数,否则这些成员会得到没定义的值。这是因为,定义在块中的内置类型或复合类型的对象被默认初始化时,它们的值将是未定义的。
②类中包含一个其他类型的成员且这个成员的类型没有默认构造函数。这是因为,编译器无法初始化这些成员,除非在类的内部把每个成员进行初始化。

(四)拷贝、赋值和析构

1、如果供我们不主动定义拷贝、赋值和析构这些操作,则编译器将替我们合成它们。一般来说,编译器生成的版本将对对象的每个成员执行拷贝、赋值和销毁操作

2、某些类不能依赖于合成的版本。特别是,当类需要分配类对象之外的资源时,合成的版本常常会失效。例如:管理动态内存的类通常不能依赖合成版本。但如果类包含vector或者string成员,则其拷贝、赋值和销毁的合成版本能够正常工作。

7.2 访问控制与封装

(一)访问说明符

1、使用访问说明符加强类的封装性:
public: 定义在public说明符之后的成员在整个程序内(通过类的实例对象访问,不管是在类内部还是其他作用域)可被访问,public成员定义类的接口(即需要给到用户的).
private: 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。
在这里插入图片描述

2、类可以在它的第一个访问说明符之前定义成员。

3、使用class和struct定义类唯一的区别就是默认的访问权限:
①使用struct: 定义在第一个访问说明符之前的成员是public的;
使用class: 定义在第一个访问说明符之前的成员是private的;

(二)友员

1、类可以允许其他类或者函数访问它的非公有成员, 方法是令其他类或者函数成为它的友员, 使用friend关键字。
在这里插入图片描述
2、友元类:类把其他的类定义成友元。如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。
在这里插入图片描述
3、令成员函数作为友元:类把其他类(已完成定义)的成员函数定义成友元

#include <string>
#include <vector>
#include <iostream>

using namespace std;

typedef std::string::size_type pos;

class Screen;//前向声明

class Window_mgr
{
public:
	void setScreen(pos x, pos y, char z);

private:
	Screen* s = nullptr;
};

class Screen
{
public:
	Screen(pos ht, pos wd, char c) :height(ht), width(wd),
		contents(ht * wd, c){ }
	friend void Window_mgr::setScreen(pos x, pos y, char z);//把Window_mgr类中的成员函数setScreen设为友员

private:
	pos height = 0;
	pos width = 0;
	std::string contents;
};

void Window_mgr::setScreen(pos x, pos y, char z)
{
	s = new Screen(x, y, z);
	std::cout << "set Screen's height is " << s->height << std::endl;
	std::cout << "set Screen's width is " << s->width << std::endl;
	std::cout << "set Screen's contents is " << s->contents << std::endl;

}

int main()
{
	Window_mgr a;
	a.setScreen(1,2,'a');
	system("pause");
}

7.3 类的其他特性

(一)在类中自定义某种类型的别名

1、由类定义的类型名字和其他成员一样存在访问限制,可以是public或者private中的一种:
在这里插入图片描述

(二)令成员作为内联函数

1、在类的外部定义的成员函数用inline关键字能把成员函数修设置为内联函数(7.1的第2条总结中提到,在类内定义的成员函数是自动inline的)。
在这里插入图片描述

(三)可变数据成员

1、const成员函数不能修改普通的数据成员,但可修改可变数据成员mutable
在这里插入图片描述

(四)从const成员函数返回*this

1、一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。

(五)类的声明

1、必须首先完成类的定义,然后编译器才能知道存储该数据成员需要多少空间。
2、只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己
3、然而,一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此类允许包含指向它自身类型的引用或指针
在这里插入图片描述

7.4 类的作用域

(一)名字查找与类的作用域

1、名字查找,即寻找与所用名字最匹配的声明过程:
首先,在名字所在的块中寻找其声明语句,只考虑在名字的使用之前出现的声明
如果没找到,继续查找外层作用域;
如果最终没有找到匹配的声明,则程序报错。
比如:

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

private:
	pos cursor = 0;
	pos height = 0;
	pos width = 0;

	typedef std::string::size_type pos;//声明在后,定义cursor是就无法找到pos,因此程序报错!
};

2、编译器处理完类中的全部声明后才会处理成员函数的定义,所以成员函数能使用类中定义的任何名字

7.5 构造函数再探

(一)构造函数初始化列表

1、如果成员是const引用或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。

2、成员的初始化顺序与它们在类定义中的出现顺序一致:第一个成员先被初始化,然后第二个,以此类推。但值得注意的是,**构造函数初始化列表中初始值的前后位置关系不会影响实际的初始化顺序。**比如:

class x{
	int i;//先出现
	int j;
public:
	//未定义的:i在j之前被初始化
	X(int val):j(val),i(j) { }//类中i先出现,所以先初始化i,而j未初始,因此i = j导致程序错误!
};

(二)委托构造函数

1、构造函数多了以后,几乎必然地会出现代码重复的情况,为了避免这种情况,可以使用委托构造函数

#include <string>
#include <iostream>

using namespace std;

class Sales_data{
public:
	//非委托构造函数使用对应的实参初始化成员
	Sales_data(std::string s, unsigned cnt, double price) :
		bookNo(s), units_sold(cnt), revenue(cnt*price) { 
		std::cout << "我是你大佬!" << std::endl;
	}

	//其余构造函数全都委托给另一个构造函数
	Sales_data() :Sales_data("NO.123", 34, 56) { 
		std::cout << "the bookNo is " << bookNo<< std::endl; 
		std::cout << "the units_sold is " << units_sold << std::endl;
		std::cout << "the revenue is " << revenue << std::endl;
	}

	Sales_data(std::string s) : Sales_data(s, 78, 90) { 
		std::cout << "the bookNo is " << bookNo << std::endl;
		std::cout << "the units_sold is " << units_sold << std::endl;
		std::cout << "the revenue is " << revenue << std::endl;
	}

private:
	std::string bookNo;
	unsigned units_sold;
	double revenue;
};

int main( )
{
	Sales_data A("NO.001", 1, 100);
	Sales_data B;
	Sales_data C("NO.321");

	system("pause");
}

运行结果
在这里插入图片描述

(三)默认构造函数的作用

1、**在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造函数。**否则,会犯不那么明显的错误:
在这里插入图片描述
2、如何使用默认构造函数
错误的使用方法:
错误的使用方法
正确的使用方法:
在这里插入图片描述

(四)隐式的类类型转换

1、如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时我们把这种构造函数称为转换构造函数

string null_book = "9-999-9999-9";
//Sales_data(const string &s) 是Sales_data类的一个构造函数
//Sales_data & combine(const Sales_data &)是Sales_data类中的成员函数
//item是Sales_data类的一个实例化对象
item.combine(null_book);//编译器自动生成一个新(临时)的Sales_data对象传递给combine

2、 编译器只会自动地执行一步类类型定义的转换,如果同时提出多个转换请求,这些请求将被拒绝。

//下列代码错误:需要用户定义的两种转换:
//(1)把“9-999-99999-9”转换成string
//(2)再把这个(临时的)string转换成Sales_data
item.combine("9-999-99999-9");

//下列代码正确:
//显示地转换成string,隐式地转换成Sales_data
item.combine(string("9-999-99999-9"));
//隐式地转换成sting,显式地转换成Sales_data
item.combine(Sales_data("9-999-99999-9"));

3、explicit构造函数:通过将构造函数声明为explicit抑制构造函数定义的隐式转换explicit只对一个实参的构造函数有效
在这里插入图片描述
在这里插入图片描述

4、explicit构造函数只能用于直接初始化
在这里插入图片描述
5、explicit构造函数不能被编译器在自动转换过程中使用,但可通过以下方式,显式地将explicit构造函数强制转换:
在这里插入图片描述

(五)聚合类

1、当一个类满足如下条件时,我们说它是聚合类
①所以成员都是public的;
②没有定义任何构造函数;
③没有类内初始值;
④没有基类;
⑤没有virtual函数。
例如,下面的类就是一个聚合类:

struct Data{
	int ival;
	string s;
}

class Data{
public:
	int ival;
	string s;
}

2、聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式(使用花括号初始化数据成员):

//vall.ival = 0; vall.s = string("Anna");
Data vall = {0,"Anna"};//初始化顺序必须与声明的顺序一致

(六)字面值常量类

1、什么是字面值常量类
(1)数据成员都是字面值类型的聚合类,是字面值常量类。
(2)如果不是聚合类,但他符合下述要求,则它也是字面值常量类:
①数据成员都必须是字面值类型;
②类必须至少含有一个constexper构造函数
③如果一个数据成员含有类内初始值,则该初始值必须是一条常量表达式;
④如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数
⑤类必须使用析构函数的默认定义,该成员负责销毁类的对象。

2、constexpr构造函数必须初始化所有数据成员,其用于生成constexper对象以及constexpr函数或返回类型。
在这里插入图片描述
在这里插入图片描述

7.6 类的静态成员

(一)静态成员

1、什么是类的静态成员?
有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。静态成员包含静态成员函数和静态成员变量。

(二)声明静态成员

1、声明静态成员:在成员的声明之前加上关键字static使得其与类关联在一起。
2、静态成员可以是public的或private的,存在于任何对象之外。
在这里插入图片描述
3、静态成员函数也不与任何对象绑定在一起,不包含this指针,即不能在static函数体内使用this指针

(三)使用类的静态成员

方法一、使用作用域运算符直接访问静态成员
在这里插入图片描述
方法二、使用类的对象、引用或者指针来访问静态成员
(注意:静态成员不属于对象,但对象、引用或指针仍然可以调用它)在这里插入图片描述
方法三、类的成员函数可直接使用静态成员
在这里插入图片描述

(四)定义静态成员

1、 和类的所有成员一样,当我们指向类外部的静态成员(成员变量和成员函数)时,必须指明成员所属的类名static关键字则只出现在类内部的声明语句中

2、静态成员函数的定义:
静态成员函数既可以在类的内部也可以在类的外部定义。
在外部定义静态成员函数时,不能重复static关键字。
在这里插入图片描述
3、静态成员变量的定义:
静态成员变量不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的,即静态成员变量不是由类的构造函数初始化的

必须在类的外部定义初始化每一个静态成员变量。

③ 静态成员变量只能定义一次

静态成员变量定义在任何函数之外,类似于全局变量,静态成员变量一旦被定义,就一直存在于程序的整个生命周期中
在这里插入图片描述

(五)静态成员能用于某些场景,而普通成员不能

1、静态成员可以是不完整类型。特别的,静态成员变量的类型可以就是它所属的类类型,而非静态成员变量只能声明成它所属类的指针或引用:
在这里插入图片描述
2、可以使用静态成员作为默认参数,而非静态成员则不可以(因为它的值本身属于对象的一部分)。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值