
C++——类和对象
概念
类似于现实中具体某个事物,和事物抽象的类别(比如我的ipad和平板电脑,西安和城市),相应的,在计算机的世界中也有对象和类的概念。用类描述一个功能和模块,并用实例化的方式创造一个对象,让他在程序中发挥作用。
类的概念有点像C语言中的结构体,但是比起结构体,类有更多丰富的属性和功能。比如在类中除了可以定义成员变量,还可以定义成员函数,以及访问限定符等等。
类的定义
定义一个类:
class classname{
void func(){
cout<<"func"<<endl;
}//成员函数
int stu;
char name[1024];//成员变量
}; //不要忘记分号
访问限定符
类的访问限定符可以通过定义访问权限的方式让一些变量隐藏起来,只向外提供一些接口给用户使用。
class student{
public: //被public修饰的成员可以在类的外部访问
void(int num){
stu = num;
}
protected:
char sex[1024];//被protected修饰的成员只能在类的内部访问
private:
int stu;//被private修饰的成员只能在类的内部访问
};
class的默认访问权限为private,而struct的默认访问权限是public(因为struct要兼容C语言)。
类的作用域:类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外定义成员时需要使用::作用限定符。再类内的成员变量相当于对于成员函数来说相当于是一个“全局变量”。
类的实例化
类是一些对象的抽象描述,就类似于数学模型和真实的物体。实例化对象就是按照数学模型“造出”真实物体的过程。和现实按照数学模型可以建造多个实体一样,一个类可以实例化多个对象
实例化对象的过程和定义变量类似:
student a;//这样就是使用了上面定义的student实例化了一个对象a
类的大小
类中既有成员函数又有成员变量时,如何计算类的大小呢?
答案是:类的大小时就成员变量之和,同时和结构体一样,类在计算大小时也要考虑内存对齐的问题。
对象的储存方式为:不同对象的成员变量单独储存,而成员函数则同用一块代码段空间。这样节省了函数重复储存浪费的空间,而每个对象的数据又不会互相影响。
特殊的情况:空类的大小为1字节(VS中)。
this指针
上面我们讲到了对象的储存方式是成员变量单独储存而成员函数公用代码段。
class number{
public:
set(int num){
ret = num;
}
show(){
cout<<ret<<endl;
}
private:
int ret;
};
int main(){
number N1,N2;
N1.set(10);
N2.set(29);
N1.show();
N2.show();
return 0;
}
但是问题来了,上面这串代码,set函数和show函数的代码是对象N1和N2公用的,那么它们是怎么知道它们需要操作的是哪个对象里的ret变量呢?
C++中使用了this指针来解决这个问题。C++编译器给每个函数增加了一个隐藏的指针,让他们指向当前运行时调用它们的对象,再函数体中所有成员函数的操作都是通过该指针去访问的,这个操作由编译器自动完成。不需要用户操作。(但用户可以操作this指针)
- this指针的类型是类类型指针常量。比如上例中的this指针的类型为number* const 。
- this指针可以看做是成员函数的一个形参。对象调用成员函数时,会将对象的地址传给this形参。对象中并不储存this指针,this指针储存在栈上。
- this指针是成员函数的第一个形参,只能在成员函数中使用。
- this指针可以为空,但这样就无法访问对象里的成员变量。
类的默认成员函数
前面我们知道了有空类,它们里面什么都没有定义。但空类中真的什么都没有吗?其实并不是,类在创建时,都会默认生成一些成员函数。这类成员函数有6个,称作默认成员函数。
构造函数
就像创建变量一样,对象在建立时,我们也想将一些变量初始化,为它们赋值。但是对于类来说,它的成员变量是不可以直接初始化的。
class time{
int hour = 0;
int min = 0;
iint sec = 0;
};//这样初始化对象中的变量是不行的
因为类是抽象的类型,并不是一个实体。没有占据内存空间,不能储存数据。 想要初始化变量,必须将所有变量都声明为公用,并按照下面的方法进行初始化。
class time{
public:
int hour;
int min;
int sec;
};
time t1 = {0,0,0};
这样虽然解决到了初始化的问题,但显然已经违背了我们保护变量的初衷。所以我们需要另寻出路,而这条出路就是构造函数。
构造函数的名字必须和类名相同,以便编译器识别它。它不具有任何类型,不具有返回值。功能由用户自定,没有定义时系统会自动生成一个无参的构造函数。
class time{
public:
viod func(){
}
time(){
}//一个没有参数的构造函数
time(int h,int m,int s){
hour = h;
min = m;
sec = s;
}//一个带参数的构造函数
private:
in.t hour;
int min;
int sec;
};
int main(){
time t1;//调用无参的构造函数,不需要加()
time t2(2,10);//调用有参的构造函数
}
析构函数
有构造函数进行初始化,就有析构函数完成对对象的资源的释放。
class sqlist{
public:
sqlist(){
_p=(int*)malloc(sizeof(int)*10);
assert(_p);
_size = 0;
_cap=cap;
}
~sqlist(){//这是sqlist类的析构函数
if(_p){
free(_p);
p = NULL;
_cap = 0;
_size = 0;
}
}
private:
int* _p;
size_t _size;
size_t _cap;
};
拷贝构造函数
就像变量一样,有时候我们希望用一个变量的初始化另一个变量。我们也希望能创建一个和原有对象一模一样的新对象。这时我们就可以用到另一个默认成员函数:拷贝构造函数。
拷贝构造函数只有一个形参,改形参是对本类对象的引用(一般用const修饰),在用已有对象创建新对象时,编译器自动调用。
class Date{
public:
Date(int year = 9102,int month = 1,int day = 1){
_year = year;
_month = month;
_day = day;
}
Date(const Date& d){//Date类的拷贝构造函数
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
其实拷贝构造函数相当于构造函数的一个重载。它只有一个参数,而且必须传引用,如果传值,会引发无限递归调用。
没有显式定义时,系统会生成默认的拷贝构造函数。默认的拷贝构造函数会按原对象的内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。如果内存中含有指针变量,这种拷贝的方式就会让指针指向同一块内存空间。这样在释放这段空间时,就会被释放两次,导致代码崩溃。
运算符的重载
运算符重载
我们想要比较字符串是否相等, 我们会本能的想到使用”==”, 但是字符串比较是需要使用strcmp函数来进行比较的. 为了能够让代码更加好看一些, 我们可以用运算符重载的方式让运算符在代码中有另外的含义.
//在全局重载 == 运算符
bool operator==(const Date& d1,const Date& d2){
return (d1.year == y2.year) && (d1.month == d2.month) && (d1.day == d2.day);
但这样重载操作符, 也破坏了类的封装性. 我们需要把它写在类内, 访问权限给以public.
复制运算符重载的注意事项:
- 不能创造新的运算符
- 重载的操作符必须有一个类类型或者枚举类型的操作数
- 内置类型的操作符不能重载, 比如int类型的 “+”操作符不能重载
- ” .* ” ” :: ” ” sizeof ” ” ?: ” ” . “不能重载
赋值运算符重载
Date& opertor = (const Date& d){
if(this != &d){
_year = d._year;
_month = d._month;
_day = d._day;
}
赋值运算符重载需要注意五点:
- 参数类型
- 返回值
- 检测是否是给自己赋值
- 返回 *this
- 一个类如果没有显式定义赋值运算符重载, 编译器会自动生成一个, 用来完成按字节序的值拷贝. (浅拷贝)
编辑器默认的复制运算符重载函数可以完成字节序的拷贝工作, 这样的拷贝叫做浅拷贝, 当类中有malloc等函数, 在堆上申请了内存空间时, 会在释放空间的时候崩溃.
const成员
const不仅可以修饰变量, 在类中, 它还可以修饰成员函数.
const修饰函数时, 实际上修饰的是该成员函数隐含的this指针, 被const修饰的成员函数不能对类的任何成员进行修改.
类中的成员函数可以通过增加const实现重载.
class Di{
public:
void func(){
cout<<"without const"<<endl;
}
void func1()const{
cout<<"with const"<<endl;
}
}
void test(){
Di d1;
d1.func();
const Di d2;
d2.func();
}
这段代码, 对象d1会调用非const的成员函数, 对象d2会调用const成员函数. 如果只有一个const / 非const成员函数, 那么const 和非const对象都会调用同意个成员函数. 另外:
- const对象不能调用非const成员函数
- 非const对象可以调用const成员函数
- const成员函数内不可以调用非const成员函数
- 非const成员函数可以调用const成员函数

