C++第十六天 类

/*
类:
	1.在类内部定义的函数默认为inline,即内联函数。
	2.在定义类函数时,加上const成员,表明不能改变其所操作的对象的数据成员,也就是只读,不能修改成员变量。const必须同时出现在声明和定义中。
	3.在类定义中,结束类定义需要加分号(;)
	4.C++类定义中,C++类定义的风格:成员函数声明定义放在成员变量的前面。
	5.C++支持在类定义外部定义成员函数,但是必须在类定义中声明了在类定义外部定义的函数。
	6.为什么类的定义以分号结束:因为类定义之后可以接一个对象定义列表,所以定义必须以分号结束,例如:
		class Test{};
		class Test{} test1,test2;

类蕴涵的基本思想是数据抽象和封装:
	数据抽象是一种依赖于接口和实现分离的编程技术。
	封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。
	数据抽象和封装的优点:
		1.避免类内部出现无意的、可能破坏对象状态的用户级错误。
		2.随时间推移可以根据需求改变或缺陷报告来完善类实现,而无需改变用户级代码。

C++的访问标号,也即访问控制修饰符,包括public、private、protected等

	使用类型别名来简化类:
		例如:
			class Screen{
				public:
					typedef std::string::size_type index;
				private:
					index cursor;
					index height,width;
			};


  类的声明与类定义:
	可以声明一个类而不定义它,例如;
		class Screen;
		这个声明,有时称为向前声明,在程序中引用了类类型的Screen。
		在声明之后、在定义之前,类Screen是一个不完全类型。
		(注:不完全类型只能以有限方式使用。不能定义该类型的对象,不完全类型只能用于定义指向该类型的指针及引用,或者用于声明使用该类型作为形参类型或返回类型的函数)
	

  何时使用this指针:
	当需要将一个对象作为整体引用而不是引用对象的一个成员时,最常见的情况是:该函数返回对调用该函数的对象的引用。
	#include<string>
	class Screen{
	public:
		typedef std::string::size_type index;
		//返回类型是Screen&,知名该成员函数返回对其自身类类型的对象的引用,即每个函数都返回调用自己的那个对象,例如:
		Screen& set(char c){
			//.....
			return *this;
		}
		Screen& move(index r,index c){
			//...
			return *this;
		} 
	private:
		index cursor;
		index height,width;
	};

  从const成员函数返回*this:
	在普通的非const成员函数汇总,this的类型是一个指向类类型的const指针,const指针表明可以改变this所指向的值,但不能改变this所保存的地址
	在const成员函数中,this的类型也是一个指向const类类型对象的const对象。既不能改变this所指向的对象,也不能改变this所保存的地址。
	(注:不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用。)
	const对象不能调用普通的成员函数,也就是说const对象只能使用const成员,非const对象可以使用任一成员,但非const版本是一个更好的匹配。


  mutable可变数据成员:
	可变数据成员永远都不能为const,甚至当它是const对象的成员时也是如此
	使用mutable声明的成员变量,任意的成员函数,包括const函数,都可以改变mutable声明的成员变量。

  使用类的成员:
	一些成员只能通过对象或指针分别使用成员访问操作符.或 -> 来访问。这些操作符左边的操作数分别是一个类对象或指向类对象的指针。
	一些直接通过类使用作用域操作符(::)来访问,如std::string。
	

  在类定义外定义类的成员函数:
	跟在类定义中定义成员函数是一样的,不同的就是要使用完全限定名,如:Screen& Screen::get(index r,index c) const{ //.... }
	如果返回类型使用由类定义的类型,则必须使用完全限定名,例如:
		//get_cursor()函数必须在类定义中声明
		inline Screen::index Screen::get_cursor() const{
			return cursor;
		}

  在全局作用域中声明了一个全局对象,如果该对象被屏蔽了,但是可以通过全局作用域确定操作符(::)类限定名字,仍然可以使用它,例如:
	//test全局定义的
	double test;
 
	void dummy_fcn(double height){
		cursor=width*::test;				
	}
 
  定义类的构造函数:
	构造函数名字必须与类名相同,构造函数可以重载,没有返回值。
	构造函数的初始化列表:
		初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员后面跟一个放在圆括号中的初始化式。例如:
			Screen::Screen(const Screen::index cs):cursor(cs),height(0),width(0){}
	没有默认构造函数的类类型的成员,以及const或引用类型的成员,不管是那种类型,都必须在构造函数初始化列表中进行初始化。例如:
		class ConstRef{
		public:
	 
			ConstRef(int ii){
			i=ii;
			//给const对象赋值,编译出错
	 		ci=ii;
	 		ri=ii;
			}
	
			//可以初始化const对象或引用类型的对象,但不能对他们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化const或引用类型数据成员的唯一机会是在构造函数初始化列表中。
			//正确的是:
			ConstRef(int ii):i(ii),ci(ii),ri(ii){}
		private:
			int i;
			const int ci;
			int &ri;
		};
	构造函数的初始化的次序与初始化执行的次序无关。成员初始化的次序就是定义成员的次序。
	
	默认实参与高燥函数:为构造函数提供默认实参,例如;
		class Test{
			public:
				//使用默认实参
				Test(const std::string &names=""):name(names),age(0){}
				//默认的构造函数
				//Test():age(0){}
			private:
				std::string name;
				int age;
		};
		对于下面任一定义,将指向为其string形参接受默认实参的那个构造函数:
			Test test;
			Test test2("name");
		在test的情况下,使用默认实参,而test2提供了一个显示实参。

  默认构造函数:
	1.合成默认构造函数:使用与变量初始化相同的规则来初始化。
	2.如果类包含内置或符合类型的成员,则该类不应该一览于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员。
	3.如果定义了其他构造函数,则提供一个默认构造函数。通常,在默认构造函数中给成员提供的初始值应该指出该对象是"空"的。
	4.使用默认构造函数:
		//错误的使用方法,假设Test是一个拥有默认构造函数的类。
		Test  test();          //test的定义将被编译器解释为一个函数的生命,该函数不接受参数,并返回一个Test类型的对象。
		
		//正确的使用方式应该是:
		Test test;     //去掉括号
		//或者这样也行
		Test test=Test();

  抑制由构造函数定义的函数转换:
	通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数。
	explicit关键字只能用于类内部的构造函数声明上。在类的定义体外部所作的定义上不在重复它。
	为转换而显示地使用构造函数,显示使用构造函数只是中止了隐式地使用构造函数,任何构造函数都可以用来显示地创建临时对象。
  (注:除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显示地构造对象。)


  类成员的显示初始化:
	对于没有定义构造函数并且其全体数据成员均为public的类,可以曹勇与初始化数组元素相同的方式初始化其成员,例如:
	struct Data{
		int ival;
		char *ptr;
	};
	Data val1={0,0};
	Data val2={1024,"aaaaa"};
	//根据数据成员的声明次序来使用初始化式。
	这种形式的初始化从C继承而来,支持与C程序兼容。显示初始化类类型对象的成员有三个重大缺点:
		1.要求类的全体数据成员都是public
		2.将初始化每个对象的每个成员的负担放在程序员身上。这样的初始化时乏味且易于出错的,因为容易遗忘初始化式或提供不适当的初始化式。
		3.如果增加或删除一个成员,必须找到所有的初始化并正确更新。
	
	

  友元(friend)机制:允许一个类将对其非公有成员的访问权授予指定的函数或类。
	友元的生命以关键字friend开始,它只能出现在类定义的内部,友元声明可以出现在类中的任何地方。
	友元不是授予友元关系的那个类的成员,所以他们不受其声明出现部分的访问控制影响。
	通常,将友元声明成组地放在类定义的开始或结尾是个好逐一。
	例如:
		class Screen{
			//建立友元关系,赋予Window_Mgr类具有访问Screen私有成员变量的权限。
			friend class Window_Mgr;
		};
		class Window_Mgr{
			public:
				Window_Mgr& relocate(Screen::index r,Screen::index c, Screen& s){
					//访问Screen 类的私有成员变量
					s.height+=r;
					s.width+=c;
					return *this
				  
				}
		};
	必须先定义包含成员函数的类,才能将成员函数设为友元。一方面,不必预先声明类和非成员函数来将它们设为友元。例如:
		class X{
			friend class Y;
			friend void f(){}
		};
		class Z{
			Y *ymem;
			void g(){return ::f();}
		};

	友元(friend)与重载函数的关系:类必须将重载函数集中每一个希望设为友元的函数都声明为友元。

	C++类静态成员(与Java的静态对象非常相似):
		1.使用static成员的优点:
			(1) static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
			(2) 可以实施封装。
			(3) 可通过阅读程序容易看出static成员是与特定类关联的。
		2.使用类的static成员(和java的static成员调用非常相似):
			通过作用域操作符(::) (Java用的是点(.)操作符)从类直接调用static成员,或者通过对象、引用或指向该类类型对象的指针间接调用。
		3.static成员函数
			static函数没有this指针:static成员是类的组成部分担不是任何对象的组成部分,所以static成员函数没有this指针。
			因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。
	static数据成员:
		static数据成员必须在类定义体的外部定义,static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。
		保证对象正好定义一次的最好办法,就是将static数据成员的定义放在包含类的非内联成员函数定义文件中。
		在类定义体外部引用类的static成员时,必须指定成员是在哪个类中定义的。然而,static关键字只能用于类定义体内部的声明中,定义不能标为static。
		特殊的const static成员:
			const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义,由于已经在类定义内部指定初始值,所以外部定义成员不必再指定初始值。

*/
#include<iostream>
#include<string>
using namespace std;
class ConstRef{
public:
	/*
	ConstRef(int ii){
		i=ii;
		//给const对象赋值,编译出错
	 	ci=ii;
	 	ri=ii;
	}
	*/
	//可以初始化const对象或引用类型的对象,但不能对他们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化const或引用类型数据成员的唯一机会是在构造函数初始化列表中。
	//正确的是:
	ConstRef(int ii):i(ii),ci(ii),ri(ii){}
private:
	int i;
	const int ci;
	int &ri;
	
};


struct X{
	//如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。
	//成员被初始化的次序就是定义成员的次序。
	//所以这个高燥函数的初始化应该是先初始化rem,然后才是base
	//但是由于先初始化rem时,需要调用base的值,而此时base尚未初始化,则应该提示警告或者错误。
	//VC++可一个通过编译,并没有警告提示,而且还能正常运行,这是为什么呢?
	X(int i,int j):base(i),rem(base % j){}
	int rem,base;	
};


 
class Screen{
public: 
	//友元
	//赋予Window_Mgr整个类访问私有成员的权限
	friend class Window_Mgr;
	//赋予Window_Mgr的成员函数访问私有成员的权限,在赋予函数访问权限时,该类必须已经被定义,
	//friend Window_Mgr& Window_Mgr::relocate(Screen::index,Screen::index, Screen&);

	typedef std::string::size_type index;
	//返回类型是Screen&,知名该成员函数返回对其自身类类型的对象的引用,即每个函数都返回调用自己的那个对象,例如:
	Screen& set(char c){
		//.....
		return *this;
	}
	Screen& move(index r,index c){
		index row=r*width;
		cursor=row+c;
		return *this;
	}
	//基于const的重载。
	Screen& display(std::ostream &os){
		//...
		return *this;
	}
	const Screen& display(std::ostream &os) const {
		//..
		return *this;
	}

private:
	index cursor;
	index height,width;
};
//友元关系

class Window_Mgr{
public:
	Window_Mgr& relocate(Screen::index r,Screen::index c, Screen& s){
		//访问Screen 类的私有成员变量
		s.height+=r;
		s.width+=c;
		return *this;		
	}
};



/*
//声明一个类Y
class Y;
class X{
	//指向类型Y的指针
	Y *y;
};

class Y{
	//指向X类型的指针。
	X *x;
};
*/


class Test{
public:
	//使用默认实参
	Test(const std::string &names="asdf"):name(names),age(0){}
	//默认的构造函数
	//Test():age(0){} 
	std::string name;
	int age;
};
int main(){ 
	Test t;
	Test t2("liangguojun");
	cout<<t.name<<endl;
	cout<<t2.name<<endl;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值