前言:
介绍c++中类和对象的基础语法和注意事项,这里是c++入门的第一道坎,细节很多,在后面的更深的学习中还会反复提到。
目录
类的访问限定符与this指针很重要,在后面会一直用,下一章将会介绍六大默认成员函数。
1.OO语言
谈到类,就离不开对象,而像我们的c++,Java也就是面对对象(Object Oriented)的语言,什么是面对对象呢?
简单来说,就是让一件事分为不同的对象,靠对象间的交互完成,但我们c++并不是完全的面对对象,因为是兼容c语言的,而c语言就是经典的面向过程的语言。
2.类的定义
c++中把结构体升级成了类(其实结构体也是类的一种),类里面可以定义函数。
class className
{
//类体,由成员函数和成员变量组成
}; //结尾带分号
-
class为定义类的关键字,className为类的名字,类中的变量称为类的属性或是成员变量
- 函数称为类的方法或是成员函数,以类定义的变量可以叫做对象
注意:
- 成员变量和函数定义的位置不会影响,类会在调用的时候在类体里面整体搜索。
- 如果是用struct定义一个类(class有些不同,下面再提),成员函数可以直接定义在类里面,也可成员函数声明和定义分离,也就是如果定义在另一个文件里面,定义时就要标明函数的来源,例如类名:函数名,声明放在头文件中时,注意缺省参数要在声明里缺省。
3.类的访问限定符与封装的引入
- public修饰的成员在类外是可以直接被访问的
- protected和private修饰的成员在类外不能直接访问
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
- 如果没有访问限定符,作用域就到类的结束
- class的默认访问权限为private,struct为public(主要因为struct要兼容c)
注意:
- 不建议使用默认的访问权限,大部分情况下成员变量不希望被改变,要用private或者protected定义(目前二者没有区别,在继承的部分会介绍区别),所以一般建议成员函数被共用就用public定义,不是就用private。
- c++中struct还可以继续当做结构体使用,class也可以但是不会使用class当做结构体。
- 成员变量定义时最好前面加上_,可以在前面或者在后面,防止成员变量与成员函数形参名相同导致出错。
- 类定义了一个新的作用域,再类体外定义成员时,需要用::作用域操作符指明成员属于哪个类域、
封装:了解了基本的类的语法,我们再来简单的谈封装,封装就是通过类将数据及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部的实现细节,控制某些方法可以在类外部直接被使用。
4.类的实例化
与结构体一样。类在创建的时候没有开空间,而创建类 类型的变量的时候(也就是对象)时开了空间,此时就能通过对象去访问类里面的成员了,这就叫做类对象的实例化,所以创建的类的成员的成员变量只是声明,并没有开辟空间。
然后,我们需要知道一个重要的点,假设我们现在定义一个这样的类:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
//一个类创建出了两个对象,但是为分别存储的(这样写是不对的,只是举例说明)
d1._year++;
d2._year++;
//一个类创建出了两个对象,分别调用的成员函数是同一个,存放在代码段
d1.Init(2023, 3, 22);
d2.Init(2024, 3, 22);
return 0;
}
d1和d2分别访问的成员变量是同一个吗?d1和d2分别访问的成员函数是同一个吗?
我们需要知道,成员变量在对象中,成员函数不在对象中,怎么理解呢?
因为每一个对象都有独立的空间,每个对象的成员变量都是独立存储的,所以两个对象访问一样的成员变量但其实不是同一个;而类里的函数是公有的,成员函数直接放到对象里面就浪费空间了,所以大小也不计算在类里,而是放到一个公共的区域(代码段),所以调用类里面的函数就去代码段里去找(汇编中call这个函数的地址,call这条指令就存放在代码段里面)
注意:没有成员变量的类的大小是多少?大小是1byte,这1byte不存储有效的数据,用来占位,标识对象被实例化定义出来了,仅仅是标记对象存在过。
5.关键字this指针(很重要)
d1.Init(2023, 3, 22);
d2.Init(2024, 3, 22);
还是上面的代码,虽然d1,d2都是调用的同一个函数,但编译器是怎么确定函数中的_year是谁的呢?这就引入了this指针:
实际传参时编译器自动传的还有调用对象的地址,然后函数中使用一个this指针接收,类型就是这个类,d1调用,this中就是d1的地址,d2调用,this中就是d2的地址。
注意:
- 函数里面可以出现this,但是形参与实参不能自己加,一般里面也不加。
- this指针是存放在栈上的(因为它是隐形的形参,vs下是通过ecx寄存器存储),不要跟代码段混淆了,this指针是形参是数据,传参需要压栈;call函数的地址是指令,指令放在代码段。
6.this空指针问题
class Date
{
public:
void Init(int year, int month, int day)
{
/*_year = year;
_month = month;
_day = day;*/
cout << this << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}
void func()
{
cout << this << endl;
cout << "func()" << endl;
}
private:
int _year=0;
int _month=0;
int _day=0;
};
int main()
{
Date* ptr = nullptr;
ptr->func();//正常运行
ptr->Init(2024, 3, 22);//运行崩溃
(*ptr).func();//正常运行
return 0;
}
现在想调用func(),直接使用->去调用会出问题吗?
分析一下,func是成员函数,但并不在对象里面,所以调用这个函数就要去call它的地址,而这句指令存放在代码段中,这里的箭头就是去Date这个类域中找func,没有解引用的意思,而传过去的ptr的地址虽然是空(this地址也是空),由于func中没有对*this解引用,所以没问题。另外,如果我们去掉ptr->就访问不到这个成员函数了,其一是因为不用对象调用函数就去全局里找么人func又只是在类这个域中,肯定找不到,其二是因为去掉就没法出传递this指针(Date::func()没有传递给this指针的内容)
那第二个为什么会崩溃呢?
因为传过去的ptr的地址为空,所以this也是空,函数里面又有解引用所以运行就崩溃了。
那第三个又是为什么正常运行呢?
这还是编译器的处理,编译器很聪明,调用类的成员函数会优先传this指针去看类里面有没有这个函数,而不是先解引用。
所以我们可以理解为在调用类的成员函数的时候使用->或者.,会优先去传this指针去类里面找(编译器的处理),而不是理解为c语言中的解引用,但如果传的是个空指针,而且这个成员函数内还对this指针解引用了,这时就会报错了。