类的引入
相信大家都知道,C 语言是面向过程的语言,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题;C++ 是基于面向对象的语言,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成,那么我们该如何定义出一个个的对象呢?
在 C 语言中,我们可以通过结构体定义一个事物的大概模样,但是这不够准确,因为 C 语言的结构体中只能定义变量;但在 C++ 中就不一样了,结构体中不光可以定义变量,还可以定义函数,因此我们可以通过结构体来很形象的描述一个事物,比如说,我们可以通过下面的代码,轻松的定义一个学生对象:
struct Student{
void SetStudentInfo(const char* name, const char* gender, int age){
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void PrintStudentInfo(){
cout<<_name<<" "<<_gender<<" "<<_age<<endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main(){
Student s;
s.SetStudentInfo("Peter", "男", 18);
return 0;
}
为了不引起混淆,C++ 在兼容了 C 语言中结构体的基础上,并引入了一个新的名词——类,并拥有新的关键字——class
。我们通过class
可以实现 C++ 中结构体能做的事情,并且更加高级。
类的定义
基本语法如下,class
为定义类的关键字,类名为类的名字,{ } 中为类的主体,注意类定义结束时后面分号。
class 类名{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
在类内定义声明
class Student{
//具体含义暂时不管
public:
//定义函数
void SetStudentInfo(const char* name, const char* gender, int age){
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
//具体含义暂时不管
private:
//定义变量
char _name[20];
char _gender[3];
int _age;
};
在类外定义声明
//为 test.h 头文件
class Student{
//具体含义暂时不管
public:
//声明函数
void SetStudentInfo(const char* name, const char* gender, int age);
//具体含义暂时不管
private:
//定义变量
char _name[20];
char _gender[3];
int _age;
};
//为 test.cpp 源文件
#include"test.h"
//需要声明函数的作用域,否则就是一个全局的函数
void Student::SetStudentInfo(const char* name, const char* gender, int age){
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
类的访问限定符及封装
访问限定符
C++ 中用类将对象的属性与方法结合在一块,让对象更加完善,不过这样无法保证数据的安全性,因此对类设置了访问权限,通过访问权限选择性的将其接口提供给外部的用户使用,使得用户既可以完成工作又不会破坏数据。
在 C++ 中存在三种访问限定符:public
、protected
、private
;访问限定符的具体说明如下:
public
修饰的成员在类外可以直接被访问;protected
和private
修饰的成员在类外不能直接被访问(此处protected
和private
是类似的);- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;
class
的默认访问权限为private
,struct
的默认访问权限为`public(默认就是不加任何限定符时的情况);
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别;也就是说,只有在语法检查时,检查代码是否可以限定规定,而在内存中的存储其实没有任何限定的。
封装
- 概念:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
- 具体实现:我们使用类来将数据属性和方法都封装到一起,对于那些不想给别人看的,我们使用
protected
和private
把成员封装起来,开放一些公有的成员函数给用户,来实现对成员合理的访问。所以封装本质是一种管理。
类的作用域及实例化
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员,需要使用::
作用域解析符指明成员属于哪个类域,否则将会和普通的定义成员一样,属于当前作用域。
class Person{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
//这里需要指定 PrintPersonInfo 是属于 Person 这个类域
void Person::PrintPersonInfo(){
cout<<_name<<" "_gender<<" "<<_age<<endl;
}
//这样定义的函数将会是全局作用域下的函数,而不属于 person
void PrintPersonInfo(){
cout<<_name<<" "_gender<<" "<<_age<<endl;
}
类的实例化
用类类型创建对象的过程,称为类的实例化。定义出一个类并没有分配实际的内存空间来存储它,只有当类实例化出对象才能占用实际存储数据,占用物理空间。因此类相当于模型,实例化相当于依据模型创建实体,因而一个类可以实例化出无数个对象。
类对象大小
由于 C++ 中兼容 C 语言,且struct
和class
除了在默认权限之外基本没什么区别,所以类对象大小的计算和 C 语言结构体大小的计算一模一样,由于在之前的博客中写到过如何计算,因此这里就不赘述了,感兴趣的可以看看我之前的博客——自定义类型:结构体、枚举、联合,具体看里面的结构体内存对齐部分即可。
this
指针
概念
class Date{
public :
void Display (){
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
void SetDate(int year , int month , int day){
_year = year;
_month = month;
_day = day;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
int main(){
Date d1, d2;
d1.SetDate(2018,5,1);
d2.SetDate(2018,7,1);
d1.Display();
d2.Display();
return 0;
}
我们观察上面的代码,可能就会有人产生疑问了,Date 类中有 SetDate() 与 Display() 两个成员函数,函数体中没有关于不同对象的区分,那当 s1 调用 SetDate() 函数时,该函数是如何知道应该设置 s1 对象,而不是设置 s2 对象呢?
这就要说到this
指针了,在 C++ 中,编译器给每个非静态成员函数添加了隐藏的指针,当函数被对象调用时,该指针就会指向调用者,在函数体中所有成员变量的操作,都是通过该指针去访问,只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成,而这就是this
指针。
这里简单说一下,静态数据成员是类的一部分,为类的所有实例共享(静态区);非静态数据成员,类的每个实例都有一份拷贝(动态区)。这个后面会详细讲的。
特性
this
指针的类型:类的类型* const
;- 只能在成员函数的内部使用;
this
指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this
形参,所以对象中不存储this指针;this
指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递;- 空对象可以正常调用没有执行解引用操作的成员函数,也就是说,如果成员函数中么有对
this
指针进行解引用的操作,那么即使是空对象也可以调用这个函数,举例如下:
class A{
public:
void PrintA(){
//这里输出 _a 相当于输出 this->_a
cout<<_a<<endl;
}
void Show(){
//这个函数中没有用到 this 解引用操作
cout<<"Show()"<<endl;
}
private:
int _a;
};
int main(){
Date* p = NULL;
p->PrintA();//报错
p->Show();//正确
}