文章目录
1 类的定义
1)类定义的变量也叫做类的实例也叫做对象,这个过程称为类的实例化。类其实是一种特殊的类型,这种类型是自己以类的形式来定义的,它其实和int
,float
没有什么太大的区别,只是类是一个比较复杂的带有函数的一个类型。
2)对象的内存空间:所有成员变量的大小之和,例如sizeof(CRectangle) = 8;
,即两个int
型变量的内存。
3)对象之间可以用=
赋值,不能用==
,!=
,>
,<
,>=
,<=
进行比较(除非这些运算符经过了重载)
class 类名
{
访问范围说明符:
成员变量1
成员变量2
...
成员函数声明1
成员函数声明2
访问范围说明符:
更多成员变量
更多成员函数声明
...
};//注意";"
//矩形类
class CRectangle{
public:
int w, h;//成员变量
void Init(int w_, int h_){
w = w_; h = h_;
}
int Area(){
return w * h;
}
int Perimeter(){
return 2 * (w + h);
}
};//必须有分号
int main()
{
int w, h;
CRectangle r;//r是一个对象
cin >> w >> h;
r.Init(w, h);
cout << r.Area() << endl << r.Perimeter();
return 0;
}
2 访问类的成员变量和成员函数
1)用法1:对象名.成员名
CRectangle r1, r2;//r1,r2是两个对象
r1.w = 5;
r2.Init(4,3);//成员函数
2)用法2:指针->成员名
CRectangle r1, r2;
CRectangle* p1 = & r1;//& r1指r1的地址
CRectangle* p2 = & r2;
p1->w = 5;
p2->Init(3,4);
3)用法3:引用名.成员名
CRectangle r2;
CRectangle & rr = r2;//rr作为r2的别名
rr.w = 5;
rr.Init(3,4);
2.1 类的成员函数的另一种写法:成员函数体和类的定义分开写
class CRectangle
{
public:
int w, h;
void Init(int w_, int h_);
int Area();
int Perimeter();
};
void CRectangle::Init(int w_, int h_) {
w = w_; h = h_;
}
int CRectangle::Area(){
return w * h;
}
int CRectangle::Perimeter(){
return 2 * (w + h);
}
3 类成员的可访问范围
private
:私有成员,只能在成员函数内被访问
public
:公有成员,可以在任何地方被访问
protected
:保护成员
类的定义:不写关键字的缺省为私有成员
class className
{
private:
私有属性和函数
public:
公有属性和函数
protected:
保护属性和函数
};
1)类的成员函数内部,可以访问当前对象的全部属性,函数;同类其他对象的全部属性,函数。
2)类的成员函数以外的地方,只能够访问该类对象的公有成员。
4 内联成员函数
内联函数:减少调用开销。成员函数也可以被定义为内联的。
有两种方式:1)inline
+成员函数。2)整个函数体出现在类定义内部。
5 重载成员函数
成员函数还可以实现重载,成员函数可以带缺省参数(不写参数就叫做缺省)。函数重载指的是具有相同函数名的一类函数,包含一个或多个不同的参数,或者参数的类型不同。
#include<iostream>
using namespace std;
class Location{
private:
int x,y;//成员变量
public://三个成员函数
void init(int x = 0, int y = 0);//规定了init的缺省值为0,0
void valueX(int val){x = val;}//函数名valueX
int valueX(){return x;}//还是函数名valueX,因为参数不同,这两个函数就构成了重载
};
void Location::init(int X, int Y){
x = X;
y = Y;
}
int main(){
Location A;
A.init(5);//y缺省,被赋为0
A.valueX(5);
cout << A.valueX();//输出5
return 0;
}
6 构造函数
成员函数的一种
1)构造函数的名与类名相同,可以有参数,不能有返回值(void也不行)
2)是用来对对象初始化,给成员变量赋值(类似于房子装修)
3)如果定义类的时候不写构造函数,则编译器生成默认的无参数构造函数(不做任何操作)
4)如果定义了构造函数,则编译器不生成默认的无参数构造函数
4)对象生成时调用构造函数
5)一个类可以有多个构造函数
为什么需要构造函数?
1)不用专门写初始化程序,构造函数可以实现初始化工作,不用担心忘了对对象进行初始化。
2)没有构造函数,可能会导致出错。
6.1 复制构造函数
copy construcor(抄袭构造函数)
1)只有一个参数,意思是对同类对象的引用。不能是X :: X ( X )
,不能是本类的对象
2)复制构造函数的定义:X :: X ( X & )
或者X :: X ( const X & )
,二者选一,后者能以常量对象作为参数
3)如果没有定义构造函数,则编译器生成默认的复制构造函数(默认的复制构造函数完成复制功能)
4)如果定义了构造函数,则编译器不再生成默认的复制构造函数,自己定义的复制构造函数不一定非要做复制的工作
6.2 复制构造函数的三种应用
1)用一个对象去初始化同类的两一个对象
2)如果一个函数的形参是类A的对象,那么这个函数在调用的时候。类A的复制构造函数将被调用,用来初始化形参。
在下面的程序例子里面:class
A有一个复制构造函数(下图标红的),在这个复制构造函数里面,我们没有做复制的工作,只是输出了一串字符。接下来函数Func
的形参是类A的对象,那么进到这个函数里面,就要生成形参,那这个形参是用什么构造函数初始化呢?就是用复制构造函数初始化的,那复制构造函数被调用是需要参数的,那这个参数是谁呢?我们就看,在main
里面Func(a2)
,以a2
作为参数。
第二种情况的总结:按以前学习的知识,形参a1
与实参a2
是相等的。但是在这里,形参a1
与实参a2
不相等,因为a1
是用自己编写的复制构造函数去初始化的,自己编写的复制构造函数并没有做复制的工作。如果不写复制构造函数,那a1
与a2
一定相等,因为编译器会自动生成默认的复制构造函数,这个复制构造函数就是做复制的工作。
3)如果函数的返回值是类A的对象,则函数返回时,A的复制构造函数就会被调用,用来初始化作为返回值存在的类A的对象
第三种情况的总结:这里就出现了个奇怪的现象,按以前学的知识,按理说Func()函数
的返回值就是b
,因为return b
。但是学了复制构造函数之后,就不能认为Func()函数
的返回值与b
相等,这取决于复制构造函数是如何编写的,因为函数的返回值是通过复杂构造函数初始化的,如果复制构造函数里面不执行复制工作,那么函数的返回值就不一定和b
相等。
6.3.类型转换构造函数
1)类型转换构造函数是一种特殊的构造函数,顾名思义,类型转换构造函数时用来实现类型的自动转换
2)特点:只有一个参数,不是复制构造函数,在类型转换构造函数时,编译系统会自动调用,生成临时的类对象临时变量
在下面程序的例子里面,语句:Complex c2 = 12;
里面=
是初始化,不是赋值。在这个的初始化里面调用类型转换构造函数时,不会生成一个临时的对象。
c1 = 9;
是赋值语句。赋值号的两边其实是不一样的类型。编译器自动调用了类型转换构造函数,生成了临时的Complex
对象,将9
作为实参,调用Complex
类型转换构造函数来初始化。
7.析构函数
析构函数就是对象消亡时做善后工作,释放分配的空间。
成员函数的一种
1)名与类名相同
2)在前面加~
3)没有参数和返回值
4)一个类只有一个析构函数
5)如果没有定义析构函数,则编译器生成缺省的析构函数(不涉及释放内存功能)
6)定义析构函数,则编译器不再生成缺省的析构函数
7.1 析构函数与数组
当数组的生命周期结束的时候,数组的每个元素都会调用析构函数。
7.2 析构函数与delete
使用delete
可以调用析构函数。
7.3 例子
8. 静态成员变量/静态成员函数
在定义成员时,前面加static
关键字,就是静态成员变量/静态成员函数
普通成员变量与静态成员变量的本质差别,有两个:
1)普通成员变量每个变量有各自的一份,而静态成员变量就一份,被共享。举例说明:修改了对象a1
的w
,a2
的w
不受影响,但是修改了a1
的nTotalArea
,a2
的nTotalArea
也受影响。
2)sizeof()
运算符不算静态成员变量的内存。静态成员变量不是都放在对象内部的,它放在所有对象的外面,被所有对象共享。
普通成员函数与静态成员函数的本质差别,
1)普通成员函数必须具体作用于一个对象(比如对象名.成员名
,指针->成员名
,引用名.成员名
),而静态成员函数并不具体作用于一个对象。
2)静态成员不需要对象就能访问
8.1 如何访问静态成员
1)类名 :: 成员名
,CRectangle :: PrintTotal();
,就调用了静态成员函数PrintTotal()
。
2)对象名.成员名
,CRectangle r; r.PrintTotal();
,不意味着PrintTotal()
作用于对象r
,只是一种形式。
3)指针->成员名
,CRectangle* p; p->PrintTotal();
,不意味着PrintTotal()
作用于p
指向的对象。
4)引用名.成员名
,CRectangle & ref = r; int n = ref.nTotalNumber();
,nTotalNumber
是静态成员变量,不能说nTotalNumber
是ref
所引用的对象r
的变量,它是对所有对象所共享。
静态成员变量本质是全局变量,静态成员函数本质是全局函数。
静态成员变量需要提前声明:如下图红色程序处
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
PrintTotal()
是静态成员函数,想把非静态成员变量w
输出,是不可以的,因为静态成员函数不作用在某一对象上面,找不到w
属于谁的。非静态成员函数里面有可能调用非静态成员变量。
9.成员对象和封闭类
成员对象:一个类的成员变量是另一个类的对象,不再是常规类型的变量。
包含成员对象的类叫做封闭类(Enclosing)
在下面的程序例子里面:CTyre(int r, int w) : radius(r), width(w) {}
是一个构造函数,用来初始化。这里使用了一种新的方式,叫做初始化列表,不再简单地用radius = r , width = w
这样的赋值语句进行初始化,把初始化的值放在小括号里面。
上面两个类构成了下面一个类。
如果CCar
类不定义1构造函数,则CCar
类编译出错,因为不知道怎么car.tyre
成员对象该如何初始化,所以在生成封闭类对象,要非常明确对象中的成员对象。
9.1 封闭类构造函数的初始化列表
初始化列表:类名 :: 构造函数(参数表) : 成员变量1(参数表), 成员变量2(参数表), ... { ... }
1)在生成封闭类对象时,先执行所有成员对象的构造函数,再执行封闭类的构造函数。
2)如果封闭类里面包含多个成员对象,这几个成员对象里面的调用顺序和成员对象在类中的说明顺序一致。
3)在消亡封闭类对象时,先执行所有封闭类的析构函数,再执行成员对象的析构函数。先构造的后析构。
下面程序中的例子: