C++是一门面向对象的语言,之前学习的C语言是一种面向过程的语言,通过学习,我们就需要知道面向过程和面向对象的区别了。本文主要介绍C++中的类和如何使用类来创建出对应的对象,这是学习C++后面更加复杂的内容的基础。
C++中的类和C语言中的结构体优点相似,但是又有所差异,C++中的类对C语言中的结构体进行了升级,在C++中,由关键字struct定义出来的东西不再叫做结构体,而是叫做类。
C++中使用struct定义出来的类和C语言中定义的结构体中有什么区别呢?
class
为定义类的关键字,ClassName
为类的名字,{}中为类的主体,注意类定义结束时后面有分号。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
通过上面的学习,我们知道C语言中使用的struct
关键字可以定义结构体,在C++中升级为类,但是在C++中我们更加喜欢使用class
关键字来定义类。因此上面的类就可以改成以下 样子:
但是此时会发现一个问题,我们在类外调用成员函数的时候发现报错了,刚刚使用struct定义类的时候并没有报错,这是为啥呢??
原因是:使用class来定义类的时候,类中的所有成员默认是私有访问的,使用struct来定义类的时候,类中的成员默认是公有的。所谓的私有,就是指在类外不能访问,只能在类内进行访问。所谓的公有,就是指在类内和类外都能够进行访问。
通常我们在设计类的时候,如果想要给别人在类外进行访问的,我们可以手动设置成公有,如果不想给别人在类外进行访问的,我们可以手动设置成私有,习惯上,我们在设计一个类的时候,通常不想要别人在类外进行访问我们类中的成员变量属性,而是通过我们在类中提供的成员函数间接访问成员变量,因此,我们会将成员变量设置成私有,将成员函数设置成公有。设置成公有时只需要在前面加上访问限定符public
,设置成私有就是在前面加上private
。
因此,解决上面的问题,我们可以考虑这样子做,具体如下:
class Person
{
public:
void Init(const char* name,int age)
{
strcpy(_name, name);
_age = age;
}
void Print()
{
cout << _name << endl;
cout << _age << endl;
}
private:
char _name[10];
int _age;
};
class Person
{
public:
void Init(const char* name, int age);
void Print();
private:
char _name[10];
int _age;
};
void Person::Init(const char* name, int age)
{
strcpy(_name, name);
_age = age;
}
void Person::Print()
{
cout << _name << endl;
cout << _age << endl;
}
在C++中的类中,访问限定符有三个:
public
,private
,protected
,访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- public:表示公有访问,也就是被public修饰的成员可以在类外和类内进行访问
- private:表示私有访问,也就是被private修饰的成员只能在类内进行访问,而不能在类外进行访问
- proteced:目前我们暂时不需要使用,所以目前我们只需要把它当成private即可。具体在继承将会进行介绍。
封装是面向对象语言的三大特性之一,封装是指用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。也就是相比于面向过程而言,面向过程中的成员变量和成员函数是分离的,因此不方便对此进行管理,在设计类的时候,我们将类的成员变量和成员函数封装在一起,并且通过类的访问限定符来限定别人对类中的成员进行访问,即允许别人直接在类外访问的成员,我们设置成公有,不允许别人在类外直接访问的成员,我们设置成私有,但是别人可以在类外间接通过类提供的成员函数进行访问。封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
C++兼容C语言,因此C++中用struct定义的类可以当作结构体进行使用,也可以当成类去使用,使用struct进行定义时,类中的成员默认是公有的,使用class进行定义时,类中的成员默认是私有的
我们定义的类也是存在一个作用域的,和局部变量存在一个作用域是一样的道理,类存在的作用域称为类的作用域,简称类域。类域是从类定义的左花括号开始,到类定义的右花括号结束。
有一些特殊场景,成员函数的声明和定义分离时,在成员函数的定义中,我们需要在成员函数名的前面指明类域,所以这一部分也是属于类域中的一部分,而不是属于类外,但是这种定义是在类外定义的,但是仍然属于这个类。
类相当于是一个模板,实际中是不存在的,通常,我们是利用类来实例化出各种对象或者说创建对象,类只是告诉我们创建出来的对象中存在什么成员,成员变量就是告诉我们这个类的属性,成员函数就是告诉如何去操作这个类,或者可以对这个类进行什么操作。比如:假如有一个日期类,那么我们可以根据这个日期类创建出很多的对象来表示日期。
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
// d1 和 d2就是根据Date类创建出来的对象
return 0;
}
我们知道,一个类可以创建出很多对象,那么我们应该如何来计算一个类创建出来的对象的大小呢??我们知道,每一个类中可能包含这个类的成员函数和成员变量,那么这个类创建出来的对象是否也会同时存在这个类的成员变量和成员函数呢??显然不是,我们知道,一个类中的成员变量表示的是这个类创建出来的对象特定的属性,那么每一个对象的属性肯定是不一样的,所以每一个对象的成员变量肯定是独有的,因此,每一个对象都应该存在独特的一份类的成员变量来表示自己的属性。对于成员函数而言,通过测试我们可以知道,不同的对象在调用类的成员函数时,如果调用的成员函数名以及参数一样,那么调用的其实是同一个函数,因此,类的成员函数并不需要在类创建出来的每一个对象中都存一份,这样会浪费空间。类的成员函数其实是存在一个公共的代码区每一个对象在调用类的成员成员时,都会去这个公共的代码区找想要调用的成员函数。所以,计算一个类创建出来的对象的大小时,并不需要考虑类中的成员函数,但是需要注意的是结构体中的内存对齐还是需要考虑的。
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
char _a;
};
int main()
{
A a;
cout << sizeof(a) << endl;
return 0;
}
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1()
{}
private:
int _a;
};
int main()
{
A1 a;
cout << sizeof(a) << endl;
return 0;
}
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
int main()
{
A2 a;
cout << sizeof(a) << endl;
return 0;
}
// 类中什么都没有---空类
class A3
{};
int main()
{
A3 a;
cout << sizeof(a) << endl;
return 0;
}
this指针是类中隐藏的一个指针,不会显示写出来,但是实际上是存在的,在类的每一个成员函数中,都会存在一个this指针是指向调用这个成员函数的对象,也就是存放的是调用这个成员函数的对象的地址。通过this指针,我们就知道是哪个对象调用这个成员函数,从而找到这个对象相关的属性,如果没有this指针,通过前面的学习我们知道,类的成员函数是存放在一个公共的代码区的,不是在每一个对象中都存放的,所以不同的对象调用同一个函数名参数相同的函数时调用的就是同一个函数,那么成员函数就无法区分到底是哪个对象调用的,就会出现问题。
平时我们的写法:并没有将this指针显然写出来
实际上:编译器会对上述的代码做如下处理
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
- this指针的特性
- this指针的类型:类类型*
const
,即this指针本身不能被改变。- 只能在“成员函数”的内部使用,并且只能是非静态成员函数,后面我们会学习类的静态成员函数,这个函数里面是不存在this指针的。
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
- this指针是类的非静态成员函数中的一个形参,所以this指针是存在于函数栈帧中的,也就是存在于栈区中。
关于this指针的一些经典题目
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void Show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
}
分析:上述代码中,p是一个空指针,程序会将p传给成员函数PrintA()中的this指针,然后通过this指针去访问对象中的_a,由于p是一个空指针,上述的操作就是通过一个空指针去访问一个对象中的成员属性,显然就是对一个空指针进行了解引用,所以会出现程序崩溃。
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void Show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Show();
return 0;
}
分析:上述代码的运行结果显然就是正常结束,虽然p仍然是一个空指针,也通过了函数传参传给了成员函数中的this指针,但是在成员函数中并没有对this指针进行解引用,所以程序不会发生崩溃。