1、类的介绍:
封装:把数值(数据)和函数打包到一个对象中,称封闭。数据和函数分别为成员,类中有数据成员和成员函数。
隐藏:OOP是根据对象来编程,不是根据组成对象的位来编程,因此隐藏数据从本质上有也有好处。
继承:基类、继承、派生。根据一个类型定义另一个类型。为类的前进锦上添花。
多态:总与指针和引用有关。一组继承性相关的类通过基类的指针或引用来操作或传送。
------------------------------------------------------------------------------------------------------------------------
本质上类,就是一种数据类型。如同结构一样内部为成员:数据成员,成员函数(有时称方法)。由类定义的变量称对象,亦称类的实例、实例化。
默认情况下:类的成员是私有的,而结构的成员是公共的。这就是为什么一般可以访问结构的成员而不能访问类的成员的原因
2、类如结构类型一样,可以用花括号进行赋值。仍按在公用成员中依次赋值。(但类中不能再有构造函数,否则会出现重定义错误)
3、构造函数。目的:确保对象的所有数据成员都设置为合适的值。
类的构造函数可有多个(重载),只要有一个,其对象不能用花括号进行初始化(会提示重定义错误)。
类内定义的函数(包括构造函数)若有代码,隐式声明为内联函数(但不一定都实现为内联函数,由编译器根据函数特性决定),类外可以显示加上inline来显示声明为内联函数。
若没有显示声明构造函数,编译器会提供一个默认的构造函数,用于创建类的对象。默认构造函数没有参数。显示的构造函数可以没有参数,也可有参数,有参数时还可以指定默认的值。注意,无参数和有参(带默认值)应注意不要发生错误。
如两个构造函数:Box();
Box(double x=1.0,double y=1.0);//注意默认值在头文件中声明中写出,实现cpp中不能再写(否则会提示出错)
这两个构造会提示候选出错,因为,当不带参数时,这两个构造函数都符合条件,编译器无法给出旗帜鲜明有答案,只有你自己去修改决定。
4、默认构造函数,在调用时不须带参数,也可以不带括号(最常见,如:Box firstBox; //此时就不带括号)
默认构造函数缺点:不能初始化非类类型的数据成员。
其实代码初始化时(CPP实现中)有两种:
一、用纯代码来进行初始化;
二、用初始化列表初始化;(加冒号,紧跟参数及括号(括号内为值),参数间用逗号分隔)
如:Box::Box(double x,double y):x(1.0),y(3.2){}
用一:首先创建数据成员,再执行赋值语句;用二:数据成员创建时就直接初始化,二比一更高效,特别是数据成员是类实例(对象)时。
5、显式声明:Explicit (用于阻止隐式转换,只能显式地使用)
当构造函数只有一个参数时,最易发生隐式转换,为了阻止这情况,必要时可显示声明。
class cube{ public:
double side;
cube{double side};
double volume(cube a);
};
cube A(3.0),B=3.0; //这里,3.0会隐匿地转换到cube类型对象,不会出错。
cout<<A.volume(5.3)<<endl;//同样,这里5.3会隐式地转换到cuble类型对象进行调用。
要想避免,可以上面构造函数前加上 explicit即:
explicit cube(double side);//这样不能隐匿的转换了,上面的两个就会出错,只有显示地用cube对象进行赋值才不会出错。
6、访问限定符:public,private,protected
其作用范围为下一个访问限定符出现之前,若一直没出现,则一直是该访问限定。
类的公共成员一般为函数,又称“类的接口”。
无论成员函数定义在什么地方,都可以在类中访问全部成员,当然成员函数也可放在私有中(当本功能实现公有中某功能,但又不对外开放时,由公有成员函数进行调用实现。
7、实例化一个对象时,必须要一个构造函数,哪怕是声明。如:Box firstBox;//如果类中没有匹配的构造函数,将会出错。
8、accessor函数(访问函数):私有数据成员常不可见,可用仅有成员函数进行访问以显示它,这类函数称访问函数(accessor函数)
mutator函数(设置函数):同样,因不可见,不易改变,可以通过公有成员函数进行修改其值,这类函数称设置函数(mutator函数)
在vb.net中,构造类时,常 用set来代替mutator函数,用get来代替accessor函数。
9、副本构造函数,又称“拷贝构造函数、复制构造函数”
当用一个已有对象去新创建另一个新的完全一样的对象时,就会用到拷贝构造函数。
拷贝构造函数没有显示定义时编译器会有一个默认的拷贝构造函数,它会复制已有对象中的“每个成员”,以创建“新的对象”。
创建新的对象时不必显示调用拷贝构造函数,只须把对象作参数“按值”传送“给函数即可。它适用于”简单的类“,复杂的须要手工进行定制拷贝构造函数,否则会出现严重错误。在第13章讨论。
与构造函数的区别:两个都有默认的函数,都有本身的限制,都可以手工定制。
构造函数是一般是对象有了,进行赋值,拷贝构造函数一般是另一个对象没有,用已有的对象创建一个新的一样的对象。
10、友元:顾名思意,就是来访朋友的成员,它当然可以访问本类中的成员,只不过访问有点特殊,须通过该类的对象进行引用。
友元其实不是本类的成员,它的到来,允许它访问类对象中非公共的成员,就象是类的成员一样。
友元可分两类:
一、函数为友元。注意函数可以是全局函数,也可以是另一个类中的函数;
二、另一个类是本类的友元。同函数友元一样也可以访问本类。
友元可以访问“类对象”的任意成员,无论这些成员的访问指定符是什么。
声明时,可将friend加在到访问类的函数上(表明这是一个外面来的朋友函数:友元),具体另一处的本体定义不用加上friend。
class A{public:
friend void show();
..........
};
void show() {...........} //不用类的限定,但如果是另一个类的函数是友元,其前有限定符,但限定符是另一个类的,而不是本类的。
11、友元函数:全局友元函数,类成员友元函数。
由于友元并不是本类的成员,所以不能用本类的访问指定符进行限定。当函数需要访问两个不同对象的内部时,可把类中成员指定为另一个类中的友元。
同样,由于友元并不本类的成员,不可以直接引用本类中成员,必须用本类的对象名来限定。
友元也是类的接口。
12、友元类:整个类是另一个类的友元。同样友元类的所有成员函数都可以不受限制地访问原类的成员。
class A {public:
friend class B;//B为A的友元。
............
};
注意:友元是单向的且不传递。即B是A的友元,但A不是B的友元,说明友元不是互惠的,只有单向。
同样:A是B的友元,B是C的友元,但是类A却不是C的友元,即友元不具有传递性。
友元类,常用于链表
13、强大的this指针: 在执行任何类成员函数时,该函数都会自动包含一个隐藏的指针,即this
尽管我们访问本类成员时不用指针什么,但实际上它通过对象的this指针来指针本对象所属的成员。只不过它一般为隐式。
用得较多的如:成员函数的参数与类中数据成员同名,比如都是a,这个时候可用this->a来限定是类中数据成员而不是参数。
还有就是返回值。比如:Box* p=new Box(1,3,4);
p->setheight(3)->setwidth(5);//这里只须将设置函数的返回值为this指针,就可这样连续设置值了。
14、this同样只能说明这个对象的指针,并不能说明这个对象相关成员的性质。因此:
class Box{public:
double volume(){return length*width*height;} //返回体积
int comparevolume(const Box& otherBox){return (volume()>otherBox.volume())?1:0;} // 这里会出错
..........
};
出错原因:声明为const说明不能修改它,otherBox通过this指针传送给函数,但并不能保证函数不能修改这个对象(因为this没有const也不能有关于const的信息),因此错误消息是“不能转换this指针”,编译器在默认情况下是不能改变对象的const性质。所以要人为进行修改,把成员函数volume()后加const,这样保证了*this指向的对象是const性质,与函数参数一致。
注意在声明和实现中都必须把对应的函数后面加上const,这与inline的声明与实现有点区别。
所以为了更大范围地使用成员函数,一般后面都加上const.
为什么要如此呢?
例如: 当一个对象A 以引用的方式或指针的方式传入了多个函数 如果都没有const保护, 那么一旦出错 根本不知道哪个函数改变了A的数据。怎么办呢?如果在不需要改变A的数据的函数参数前 加上const, 那么就能确定在这个函数中不会改变A的数据, 对于排错非常有用。当然const还有其它好处。
15、const对象、const成员函数。
本质:const对象:它只能调用const成员(数据或函数),若调用不是const的成员,将会出错。就是14所说那样。
const成员函数:不能修改“类对象”的数据成员,即在其代码内数据成员不能作为左值。
如果在const成员函数中修改本类的数据成员,将出错提示”只读“:[Error] error: assignment of data-member `Box::length' in read-only structure
即使是通过this指针进行调用也一样。因此,两个成员函数完全一样,仅后面const不一样时,它表明是两个函数,是可以进行重载的。
如果const对象调用非const成员函数会提示:error: passing `const Box' as `this' argument of `double Box::volume()' discards qualifiers,意思是丢失类型限定,因为常量对象传给函数使用this指针(默认)来指定,但this没有办法把const这个性质带过来,所以提示这个错误。
16、豁免权:mutable数据成员(可变数据成员)
由于const对象只能调用const成员函数,const成员函数中不能修改数据成员,但有时必须修改,为此产生一个专门的豁免权的mutable性质,它来说明const成员函数中的数据成员是可以修改的(不必只读)。比如远端服务传到本地数据,其数据只读,有时要更新这个缓冲区,即使它声明是const,这时就要用到mutable.
class A{ public: bool islocked() const { time=getcurrentTime(); //常成员函数,一般是不能修改数据成员的,但因后面有mutable限定,可作左值
return lockstate();
}
.............
private: mutable int time;//指定当为const时是可以修改这个数据成员的,否则会提示”只读“
............
};
17、常量的强制转换:目的:只是为了参数的匹配
const_cast<类型>(表达式) //注意,这个”表达式“结果类型只能是常量const
强制把const转换为其它类型,只是为了匹配参数的一致,或其它使用情况,使用它应确保不能破坏原常量的性质(修改),即使修改也是无效的。
void changeconst(const int* p){
int* newp=const_cast<int*>(p);//强制把const属性去掉
*newp=150; //新的指针试图去修改
}
int main(int argc, char *argv[]){
const int a=30;
changeconst(&a);
cout<<a<<endl;//结果还是30,并不是150,因为它“固执地认定为30。修改无效
return 0;
}
可以看到想通过取巧的方式去修改原参数,结果修改无效。
18、对象数组:同其它一样,可以设置对象的数组,如:Box boxes[10];//然后单独引用每个元素如同单个对象一样
注意:每个数组元素都会调用构造函数一次。
19、边界对齐:有些机器因性能原因,两字节的变量必须放在2的倍数的地址中,同样4字节变量必须放在4的位置的地址中,同理8字节。。这样不同值在内存中留下一定的空隙。比如:在类中有两个数据成员:int a; double b; //int占4字节, double占8字节,int占后,不满足8的倍数,须空出4字节,再紧跟8字节,这样实际上就占用了16字节,而不是12字节。也许你会说,int和double换个位置不就行了?不行!!!!因为类还要为对象考虑数组的情况(对象数组),对象数组要求每个对象都放在8字节的倍数的地址上。
注意:类对象一般只考虑类中数据成员的大小(注意,它不会计算静态成员数据,因为静态数据不属于对象,它只属于类)。
不管是成员函数,还是非成员函数,静态的和非静态的,函数不会因为对象的多少而存在不同数量的副本,也就是,代码只有一份。
数据成员却因对象的不同有对应的副本,即有3个对象即3个副本。静态数据成员只有一个副本。
20、特权成员:静态成员:它只记录类的属性,不是记录各对象的属性。因此它独立于任何对象,只属于类,不属于对象(从计算对象大小可以看出,没有静态成员的大小)
类对象的大小不包含静态数据成员的大小;在创建类对象前静态成员已经存在(初始化)并可调用;
非静态函数成员都有this指针,指向调用函数的当前对象,但静态函数成员不包含this指针。
声明须在类内中前加static,定义则必须在类外(或类的实现中),且前面不能再加static(否则将被认定为另一个全局变量)
注意:因静态数据成员不属于对象,所以对于常量成员函数,它是可以修改静态数据成员的(其它的除mutable外是不能修改的)
同样为了定位一个可以比较的标准,也可以把静态数据成员设定为常量:const static int a;//这样就不可修改它。
21、静态成员函数:只须在成员函数前加static即可定义。
因为静态成员函数是没有this指针,因此,它是不能声明为const(const的实质是不能修改对应对象的数据成员),所以同样它也不能访问调用它的对象。
另外静态成员中参数如果是对象,这个对象在静态函数中是可以任意访问私有和公有成员的,但这没意义,一般都是通过对象的成员函数进行访问私有。
class A{public: static int show(A& d){ return d.a;} //注意这里a是对象d的私有。因为静态是可这样用的
private: int a,b;
};
A *p=new A(3,4);
cout<<p->show(*p)<<endl; //可以用指针
尽管静态成员是没有this指针的,但对象的指针仍然可以访问。或者直接用对象加点号来引用它们。
同样静态成员不属于特定对象和this指针,因此不能用它“直接”访问调用它的对象,只能用传递参数的形式,如上面的例子。
为什么静态成员函数中的对象可以访问私有呢?因为静态函数是属于类的,类中的成员是可以任意访问其它成员(仅类外对象可访问公有),于是它就赋予了函数内代码全权访问权限。一句话,静态成员函数具有全部访问权限。再换句通俗的解释,因为静态函数是属于类,它是类的成员,因此它是可以访问类的任意的成员(包括私有),但是因为这个是静态函数,它不能明确到到底是哪个对象的东西,前面加上对象是限定指明这个成员(私有成员)是那一个对象的,而不是别的。如果你不指明,静态成员就没法明确了。