本文共 10885 字,大约阅读时间需要 36 分钟。
类是对象的抽象,对象是对客观事物的抽象。
用通俗的话来说:
类是类别的意思,是数据类型。
对象是类别下的具体事物。
也就是说:
类是数据类型,对象是变量。
比如:
水果是类。(水果不是真实存在的)
一个苹果是具体的对象。(一个苹果是真实存在的,它有大小,有颜色)
定义一个类:盒子Box
class Box{ public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度}; 解释1:
class是一个专门用于定义类的关键字。
Box为类名 public下面会讲到,这里不必在意。 double length是类的数据成员,这里一共定义了三个数据成员。分别代表Box的长宽高。 将类的成员使用大括号{}包裹,记得最后使用分号结束。
解释2:
盒子是有自己的属性的,比如说盒子的长宽高,盒子的颜色和材质(上面的代码没有定义)。
我们称这些属性为类的数据成员。尽管有数据成员,类是没有存储空间的,只是一段代码存储在内存中。(因为类是抽象的,不是真实存在的)
只有实例化为对象,才会创建内存空间供数据成员存储。 也就是说,每一个盒子都会有自己的大小。 比如,int是没有存储空间的,但是int i=0;就会为变量i申请4字节的内存空间。
class Box{ public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度};Box box1; //使用类创建对象box1box1.breadth = 20; //将对象box1的数据成员breadth的值修改为20box1.height = 20;box1.length = 20;//得到box1的数据成员的值,再得到盒子box1的体积cout << "盒子的体积为" << box1.breadth* box1.height*box1.length << endl; 解释:
Box为类名,是数据类型。
box1为变量名,是对象。它是通过类Box实例化的一个对象。 可以看到类就像是一个模板,对象是根据这个模板创建的。 box1.breadth表示访问对象中的数据成员breadth。 访问数据成员的语法:对象名.成员名。
分析:我们一般是通过【对象名.成员名】。如果使用动态内存分配,那么我们就没有对象名了,只有指针名。
class Box{ public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度};Box *p = new Box; //使用动态内存分配的方式创建对象p->breadth = 20; //使用指针修改对象的数据成员p->height = 20;p->length = 20;//使用指针得到对象的数据成员的值,在得到盒子的体积cout << "盒子的体积为" << p->breadth * p->height * p->length << endl class Box{ public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(){ //成员函数 return length * breadth * height; }}; 解释:
成员函数定义在类中,可以访问对象中的所有成员(包括数据成员和成员函数)。
(有些数据成员是不能被随便访问的,public的作用就是限制访问的,后面会讲到)
成员函数没有内存空间:
数据成员在每个对象中都有内存空间,但是成员函数在存在类中,在内存中只是一段代码,需要被对象使用时,则被调用。
class Box{ public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(){ return length * breadth * height; }};Box box1;box1.breadth = 20;box1.height = 20;box1.length = 20;//调用成员函数得到对象的体积cout << "盒子的体积为" << box1.getVolume() << endl; 解释:
成员函数的调用方法与数据成员一致,通过【对象名.函数名(参数)】调用。
函数可访问此对象的所有成员。
普通函数中,可以先声明再调用最后定义。
在类的成员函数中,同样可以进行函数声明。
class Box{ public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(); //成员函数声明};double Box::getVolume(){ //在类的外面进行函数定义 return length * breadth * height;} 解释:
使用【类名::】来修饰函数,表示此函数是Box的成员函数。(其他类可能也有同名的函数)
在调用时,是使用【对象名.函数名(参数)】调用的。
为什么要这么做?
因为函数具有封装性,我们只要关心函数的使用,不必关心函数是如何实现的。
数据封装是面向对象编程的一个重要特点,类的访问修饰符防止直接访问类的内部成员。
类成员的访问限制是通过标记public、private、protected来指定的。 关键字public、private、protected称为访问修饰符。
比如:
class Base { public: // 这里是公有成员 protected: // 这里是受保护成员 private: // 这里是私有成员}; 公有成员在程序中类的外部是可访问的。也就是说,谁都可以访问这些公有成员,没有任何限制。
class Box{ public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度};void main(){ Box box1; box1.breadth = 20;} 我们发现:可以直接通过【对象名.成员名】访问对象的成员。
相对的,我们可以看一下private和protected是都能通过这种方式直接访问对象的成员。
私有成员在类的外部是不可访问的,甚至是不可查看的。
也就是说,在类的外部是不可以通过【对象名.成员名】访问对象的成员的。 类的私有成员只能在类的内部被直接调用。
class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度};void main(){ Box box1;// box1.breadth = 20; //这行代码会报错!} 解释:
在以上的代码中,由于breadth是私有成员,所以直接通过【对象名.数据成员】的形式访问会报错。
类的私有成员是默认的成员,如果没有写访问修饰符,则默认为private。
比如:
//下面的数据成员都是privateclass Box{ double length; double breadth; double height;}; 通过函数间接访问private:
如果我们在类的外部需要访问私有成员,我们可以通过函数间接访问。
原理:1.private不能被直接访问.
2.public可以被直接访问。 3.类的内部可以随意调用任意成员。 得到结论:通过public成员间接调用private成员。
xclass Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度public: void setBreadth(double B){ Breadth = B; } double getBreadth(){ return breadth; }};void main(){ Box box1; box1.setBreadth(12.5); cout <<"盒子的宽度为: "<< box1.getBreadth() << endl;} 解释:
length、breadth和height都是私有成员。
setBreadth(double B)和getBreadth()是公有成员。 我们通过这两个set和get函数修改和得到breadth的值。 通过这样的方式,我们就可以访问私有成员了。
保护成员访问控制与私有成员十分相似。在类的外部不可被直接访问。
但有一点不同,保护成员在派生类(即子类)中是可访问(以后再说)。
什么是构造函数?
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
而且这个构造函数时自动执行的,无需手动调用。
构造函数有什么作用?
构造函数可用于初始化,例如:为某些成员变量设置初始值。
如何定义构造函数?
- 构造函数的名称与类的名称是完全相同。
- 不会返回任何类型,也不会返回 void。
class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度public: Box(double L,double B,double H){ //构造函数 length = L; breadth = B; height = H; } double getBreadth(){ return breadth; }};void main(){ Box box1 = Box(12, 12, 12); //定义的时候初始化 cout << "盒子的宽度为: " << box1.getBreadth() << endl;}
- 类中带有默认构造函数,若没有定义构造函数,则会采用默认构造函数创建对象。
- 默认构造函数是没有参数的,不会初始化数据成员。(就是空的函数,什么事也不做)。
- 若自定义了构造函数,则C++不会为我们创建默认构造函数。 我们可以进行构造函数重载,使得初始化多样化。
例1:
Box(){ //默认构造函数,由程序自动创建}//如果我们自定义了其他函数,则程序不会为我们自动创建构造函数 例2:
#includeusing namespace std;class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度};void main(){ Box box1=Box(); //此处不会报错,因为自动创建了构造函数Box(){}}
例3:
#includeusing namespace std;class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度public: Box(double L, double B, double H){ //构造函数 length = L; breadth = B; height = H; }};void main(){ Box box1; //报错!!!!不存在默认构造函数}/*解释: 因为我们自己创建了一个构造函数,所以程序不会为我们创建默认的构造函数Box(){} 如果想要有默认的构造函数:可以使用函数重载,自己创建Box(){}*/
例4:
#includeusing namespace std;class Box{ private: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度public: Box(double L, double B, double H){ //构造函数 length = L; breadth = B; height = H; } Box(){ //构造函数 }};void main(){ Box box1 = Box(11,12,14); Box box2; //相当于Box box2=Box();}
什么是析构函数?
类的析构函数是类的一种特殊的成员函数,它会在每次删除对象时执行。
如何定义析构函数?
1.析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀。
2.它不会返回任何值,也不能带有任何参数。
析构函数有什么作用?
析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
比如:在构造函数中使用了动态内存分配,那么系统并不会自动回收这些内存。所以需要析构函数。
如何调用析构函数?
无需手动调用,在程序运行完后自动调用析构函数,完成内存释放和文件关闭等功能。(所以只要关心如何定义即可)
class Arr{ public: int *p; Arr(){ //构造函数 p = new int[5]; } ~Arr(){ //析构函数 delete[] p; }};/*解释: 析构函数作用其中之一就是回收动态内存分配的空间。 new和delete要成对出现。 将delete写在析构函数中,就可以在销毁对象时自动回收内存了。 析构函数还有其他的作用:比如关闭文件。*/ 什么是拷贝构造函数?
拷贝构造函数是一种特殊的构造函数,它在创建对象时,可以用对象给对象赋值。
拷贝构造函数有什么作用?
如果对象中有指针,那么使用对象给对象赋值后,它们的指针将会指向同一块内存。这样它们就会产生关联,而不是两个不同的对象。
如何定义拷贝构造函数?
类名 (const 类名 &obj) { //省略构造函数的主体} 例1:
/*这个例子没有使用拷贝构造函数,但是运行结果是正常的。例子中:使用了对象给对象赋值。对象给对象赋值的过程中,会将所有成员变量的值复制一遍。*/#includeusing namespace std;class Box{ public: double length; // 盒子的长度 double breadth; // 盒子的宽度 double height; // 盒子的高度 Box(double L, double B, double H){ //构造函数 length = L; breadth = B; height = H; }};void main(){ Box box1 = Box(1, 1, 1); Box box2 = box1; cout << box2.length << endl; //输出1}/*解释: 这个例子中:将box1的值复制一份给box2。*/
例2:
#includeusing namespace std;class Arr{ public: int *p; Arr(){ //构造函数 p = new int[5]; //动态内存分配5个int的空间 } ~Arr(){ //析构函数 delete[] p; //回收动态内存分配的空间 }};void main(){ Arr a; a.p[1] = 1; //将a中的p[1]修改为1 Arr b = a; b.p[1] = 2; //将b中的p[1]修改为2 cout << a.p[1] << endl; //输出2!!! cout << b.p[1] << endl; //输出2!!! getchar(); getchar(); getchar(); getchar(); getchar();}/*现象: 我们将b中的p[1]修改为2,但是a中的p[1]也被修改为了2. 我们并没有修改a中的p[1],为什么也变成了2?解释: Arr b = a;将a的值赋值给了b。 那么,a的值就和b一样了,即他们的数据成员p指向的内存空间是同一个。 所以,修改b的值,a的值也会跟着变化。*/
产生的问题:
我们想要创建的是两个不同的对象,但是上面这个例子:两个对象相互关联了。
如何解决:
拷贝构造函数
#includeusing namespace std;class Arr{ public: int *p; Arr(){ p = new int[5]; } ~Arr(){ delete[] p; } Arr(const Arr&obj){ //拷贝构造函数 p = new int[5]; for (int i = 0; i < 5; i++) p[i] = obj.p[i]; }};void main(){ Arr a; a.p[1] = 1; Arr b = a; b.p[1] = 2; cout << a.p[1] << endl; //输出1 cout << b.p[1] << endl; //输出2 getchar(); getchar(); getchar(); getchar(); getchar();}/*解释: 有了拷贝构造函数,对象给对象赋值的时候就不会只复制地址了,而是复制地址所在的内存空间的值。*/
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;
友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
使用关键字friend来定义友元类和友元函数
class Box{ double width;public: friend void printWidth(Box box); void setWidth(double wid){ width = wid; }};void printWidth(Box box){ cout << box.width << endl;}void main(){ Box b; b.setWidth(10); printWidth(b); //外部函数访问private成员} class Box{ double width;public: friend class friendBox; void setWidth(double wid){ width = wid; }};class friendBox{ public: void testFunc(Box box){ cout << "box.width = " << box.width << endl; }};void main(){ Box b; b.setWidth(10); friendBox f; f.testFunc(b); //友元类中的所有函数都是友元函数} 背景:
函数在调用的时候会发生转移,会在栈中保存现场(比如原来的变量值),这样的操作会花费一定的内存空间和时间。
内联函数:
在调用时不发生控制转移,而是在编译时将函数体嵌入在每一个调用处。
适用于功能简单,规模较小又使用频繁的函数。 好处:加快程序运行效率
注意:
inline int Max(int x, int y){ //将函数声明为内联函数 return (x > y)? x : y;}int main(){ cout << "Max (20,10): " << Max(20,10) << endl; return 0;} 每一个对象都有自己的地址,this指向自己。
可以通过this来访问本对象的成员。
class Box{ public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double setLength(double length){ this->length=length; //通过this指针访问自身的数据成员 }};int main(){ Box box1; box1.setLength(10); //长度被修改为10 cout< < 解释:
细心发现,函数
那么我们如何去访问数据成员length呢? 因为this指针永远指向自己(本对象),所以可以通过this->length来访问。double setLength(double length)中的length和数据成员中的额length冲突了,函数参数length比数据成员length的作用域更小,所以默认访问的是函数参数length。
静态成员在类的所有对象中是共享的。无论创建多少对象,静态成员始终只有一个。
我们使用static关键字将成员声明为静态。 静态成员分为静态数据成员和静态成员函数。
静态数据成员需要在类中定义,在类外初始化。
访问方式:
类名::变量名(推荐)对象名.变量名。
例1-静态成员是共享的:
class A{ public: static int i; //静态数据成员定义};int A::i=0; //静态数据成员初始化int main(){ A a1,a2; a1.i=1; cout< < 解释:
我们将a1的i修改为1,可以发现a2的i也变成了1,所以静态成员是共享的。
例2-静态数据成员的作用举例:
class A{ public: static int ObjectCount; //静态成员 A(){ ObjectCount++; //每创建一个对象,此变量加1(用于记录对象的数量) } ~A(){ ObjectCount--; //每回收一个对象,此变量减1 }};int A::ObjectCount=0; //静态成员初始化void main(){ A a1; A a2; cout< < 解释:
静态数据成员是共享的。在构造函数中,将此变量+1;在析构函数中,将此变量-1。
那么,这个成员就是用来记录对象的数量。(当前存在多少个A的对象)
静态成员函数只能访问静态成员(包括数据成员和静态成员函数)和类外部其他函数。
静态成员函数没有this指针。
解释:
因为静态成员函数只有一份,那么如果他要访问对象中的数据成员,该访问哪一个对象呢?所以,不能让静态成员函数访问对象中的非静态成员。
this指针是每个对象都有的,指向本对象。静态成员是类拥有的,而不是对象,所以类没有this指针,静态成员函数也就不能使用this指针了。
访问方式:
类名::静态函数名(推荐)对象名.静态函数名
举例:
class A{ public: static int ObjectCount; //静态成员 A(){ ObjectCount++; } ~A(){ ObjectCount--; //每回收一个对象,此变量减1 } static void print(){ //静态成员函数 cout< < 转载地址:http://hfbj.baihongyu.com/