Learn:C++ Primer Plus Chapter10

第10章 对象和类

Abstract

This 章介绍的は一些基本的 EastWest of 类,具体用法方面没有什么太多的东西。

  • 类是用户定义的类型,对象是类的实例。

1.抽象と类

1.1 访问Control

class 中有两个关键字:privatepublic。对于 private 部分只能通过成员函数或者 friend 函数进行访问,而无法通过 class 之外的其他函数进行访问。也就是 public 函数是程序和对象私有成员之间的桥梁,提供了对象和程序之间的接口。当然这些都是 编译层面的限制,在二进制文件调试时是可以自由访问的。

struct 关键字在 C++ 中也得到了 extend,可以 like 类一样添加成员函数,与 class 唯一的不同在于,struct 的默认属性是 public,而 class 的是 private

class Good {
// the default attribute is private in class, so this private can be omitted.
private:
	// this variable can only be access by the member function.
	int a;
public:
	// this function can be access by all.
	void func1();
	
	// declare a friend function which can access the private member, 
	//but it self is not the member of this class
	friend void func2();
};
struct Very {
// the default attribute is public in struct, so this public can be omitted.
public:
private:
	Good* pGood;
};

The 主要目标 of OOP(Object Orient Program)是隐藏数据,so 一个 recommended 用法是将数据放入到 private 部分,而接口的成员函数放入到 public 部分

1.2 成员函数

这里唯一想要说明的是 成员函数友元函数declarationdefinition 上面的区别。

  • 成员函数在声明时只有在 public 的部分才能被外部访问,友元函数 则被添加在 public 部分。
  • 添加到类中的 友元函数 只是在 中添加,并不能算作函数的声明,需要在类外进行额外的声明才行。
  • 成员函数 中具有 this 指针,也就是其 calling convention__thiscall in x86,而 友元函数 则没有 this 指针。其只是获得了访问 类中 private 成员的权限。
// the declaration in a .h file
class Good {
	int a;
	int b;
public:
	// declaration of the member func1
	void func1();
	// add the func2 as frined, but this is not a declaration of func2
	friend void func2();
};
// declaration the func2
void func2();
// the definition in a .cpp file
// the member funtion must use the namespace operator ::
void Good::func1() {
	// have the this pointer
	return a;
}

// friend function definition just as a normal function
void func2() {
	// friend function don't possess the this pointer, 
	//but it can access the private part of the class
	Good good;
	return good.a + good.b;
}

下面简单介绍下 const 成员函数,它存在的意义与就是声明该成员函数不会对于类中的数据进行修改。下面的使用实例胜过千言万语。

class Good{
public:
	// declaration a const member function
	void func3() const;
};

//define the const member function
void Good::func3() const {
	return;
}

2. Construct と Destruct

2.1 构造函数

  • when:after the class 内存分配之后。
  • functionality:initialize the 内存。
  1. 定义 Construct 函数
    下面的代码演示了如何定义一个 class构造函数,构造函数需要对类中的变量进行初始化,如果构造函数中接受到的参数与 class 中的数据重名,则需要使用 this 指针来使用自身的数据,because 数据作用域的匹配规则是 由近及远
class Good {
	int a;
public:
	Good(int a) {
		// reference class a need use the this pointer
		//if don't add this pointer the same name variable will use the argument firstly 
		//rather than the class data
		this->a = a;
		return;
	}
};
  1. 使用构造函数
    构造函数使用时分为 隐式调用显示调用,这两种方式在 二进制层面等价,都是调用相应的构造函数对于 class 中的数据进行初始化。C++ 中经常使用 new 运算符来动态分配类的内存,可以在申请时隐式的调用相应的构造函数。还有另一种用法,不会主动触发 构造函数reinterpret_cast 关键字会将一块内存空间按照类的数据结构进行解析,也可以使用相应的成员函数对于数据进行处理,一般情况下不会这么使用,主要是在逆向分析时,方便动态调试。
// explict call the construct function
Good cl1 = Good(1);
// implict call the construct function
Good cl2(1);
// use the new operator to malloc the memory
Good* pcl = new Good(10);

// use the malloc to allocate the memory, this usage is not usual
//mainly use in the reverse dynamic analysis
void* p = malloc(sizeof(Good));
Good* pc2 = reinterpret_cast<Good>(p);
  1. 默认构造函数
    所谓默认构造函数就是一个不带有任何参数的构造函数,也就是让 Good a; 能够合法通过编译的函数。编译器 if and only if 没有定义任何任何构造函数时,才会提供默认构造函数。如果 Programmer 定义其他包含参数的构造函数(如:Good(int a)),而没有定义默认构造函数,那么上述代码就会出错。这样做是编译器为了 防止出现未初始化的内存。有两种定义默认构造函数的方式:
  • 给已知构造函数提供参数
// the default argument is 1
Good(int a = 1);
// so the usage  is valid, and member a will be set to 1;
Good cl1;
  • 重载一个默认构造函数
class Good {
	int a'
public:
	Good(int a) {
		this->a = a;
	}
	// overload the construct function, and call a haing parameter construct function
	Good() {
		Good(0);
	}
};
// valid usage,and member will be set to 0
Good cl1;

2.2 析构函数

  • when:before the class 内存释放之前。
  • functionality:释放其他资源。
  1. 定义析构函数
    析构函数需要完成对于资源的释放,释放的资源主要是通过 newmalloc 申请到的堆空间(因为栈中的内存会被程序自身维护的很好)。
struct Very {
private:
	Good* pGood;
public:
	~Very() {
		if(pGood != NULL) {
			// release the memory
			delete pGood;
			pGood = NULL;
		}
	}
};
  1. 调用构造函数
    调用构造函数的任务由 编译器完成 完成,不需要程序员去手动调用(千万不要去手动调用析构函数,会出现内存释放错误)。
  • 栈中的类 会在函数结束时调用相应的析构函数,对于这些数据编译器会将析构函数的调用放置在函数的结尾处,所以不是 Programmer 不调用,而是编译器帮我们调用了。
  • 堆中的类 会在使用 delete 运算符时调用相应的析构函数(应该不会有人用 mallocfree 这对函数来控制类的内存吧)。
  • 一句话概括析构函数:你不可以用,但是不能没有

3. this 指针

this 指针用处很大,可记录下的内容却很少,所以这部分很简单,一句话就能够概括:this 指针用来区分你我

下面的代码中就是 this 指针的常用方式,总之当需要区分你我时,就用 this 准没错。

class Goodvery {
	int nVal;
public:
	Goodvery(int nVal) {
		// use the this to get the self data
		this->nVal = nVal;
	}
	
	const Goodvery& Compare(const Goodvery& a) {
		if(nVal > a.nVal) 
			// return self using this
			return *this;
		else
			reutrn a;
	}
};

4.类Scope

4.1 常量 in class scope

类作用域中的常量是由所有的类成员共享的,它的使用场景就是一个Value which所有的实例中到需要使用。可以使用两种方式来实现这个目标

  • 枚举 enum
    枚举实现的思路与 C/C++ 中的宏定义符号 #define 有着异曲同工的设计,并不会创建数据成员,而是在编译时使用相应的值进行替换。
class Calendar {
	// there is not space allocate for Mouths to save
	enum {Mouths = 12};
	// the compiler will replace the Mouth symbol with 12
	int Day[Mouths];
};
  • 静态数据 static
    使用 static 关键字修饰之后的变量会作为静态变量的一部分存放到程序的 .data 节中,使得其能够被所有的类实例所共享。当然如果你不加 const 进行修饰的话是可以被更改的,就如同一个普通的全局数据一样。
class Calendar {
	// the Mouths will save with other static data in .data section
	static const int Mouths = 12;
	int Day[Mouths];
};

4.2 枚举 in class scope

C 语言中的枚举存在诸多问题:

  1. 名称冲突
    下面代码如果交给编译器就会出现符号的多重定义,原因是 egg Smallt_shirt Small 位于相同的作用域。
enum egg {Small, Medium, Large, Jumbo};
enum t_shirt {Small, Medium, Large, Xlarge};
  1. 隐式转化
enum egg_old {Small, Medium, Large, Jumbo};
egg_old one = Medium;
int king = one;	//implict type conversion for unscoped

C++ 中对于枚举则提供的作用域的支持,可以避免出现 名称冲突隐式转化,其定义的方式就是在 enum 关键字的后面添加 class or struct 关键字即可完成转化。

// there is no collasion in enum name between two scope
enum class egg {Small, Medium, Large, Jumbo};
enum struct t_shirt {Small, Medium, Large, Xlarge};

t_shirt rolf = t_shirt::Large;
int ring = rolf;	// not allowed, not implicit type conversion 

如果需要的话,可以进行显示的类型转化。

int Frodo = int(t_shirt::Small); // Frodo set to 0

还可以通过 : type 的方式来指定底层的实现类型。当然底层类型必须是整形的,不然枚举就无法自行完成了。这样的指定是用来显示枚举数据的存储字宽。

enum class : short egg {Small, Medium, Large, Jumbo};

5.对象数组 and 抽象数据类型

5.1 对象数组

创建数组是一个非常常见的操作,对于 class 关键字创建的自定义类型也可以创建数组,只不过创建时会调用 构造函数 罢了。

class Example{
	int a;
	int b;
	int c;
public:
	Good(int a = 0, int b = 0, int c = 0) {
		this->a = a;
		this->b = b;
		this->c = c;
	}
}

  • 对于数组的初始化,可以 like C 语言的结构体那样使用 {1, 2 ,3} 进行赋值,当然必须要有 与参数匹配的构造函数
Example array1[3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
  • 也可以使用显示调用构造函数进行赋值。
Example array2[3] = {Example(1, 2, 3), Example(4, 5 , 6), Example(7, 8 , 9)}; 
  • 当然也可以进行部分初始化,但必须要有 默认构造函数。创建时会首先处理显示调用的构造函数,对于没有显示调用的则会隐式的调用默认构造函数完成初始化。
// the array3[0], array3[1], array3[2] will call the matched construct function
//array3[3-9] will be initialized using the default construct function
Example array3[10] = {Example(1, 2, 3), Example(4), Example()};

5.2 ADT(Abstract Data Type)

ADT 以通用的方式描述 数据类型,而没有引入语言或者实现细节。也就是对于数据结构进行封装。我这里直接照搬书中的例子,个人感觉需要与 模板 十分相似。

下面的程序在需要改变数据的长度时,只需要将 typedef 定义 alias 对应的具体类型更改即可,而不需要重新编写代码。

typedef unsigned long Item;

class Stack {
	enum {MAX = 10};
	Item items[MAX];
	int top;
public:
	Stack();
	bool isempty();
	bool isfull();
	bool push(const Item& item);
	bool pop(Item& item);
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值