C语言是一门面向过程的语言,C++是一门面向对象的语言,本节将会初步讲述面向对象的编程思想以及类的创建。
编者注:从本讲开始在代码模块不会全部包含头文件,一些常见的头文件请读者在cv大法后自行添加。
目录
一、面向过程与面向对象的初步认识
大象塞冰箱是C语言中最常见的模型之一,步骤可以简化为上图所示。该操作能实现将大象塞进冰箱的操作,关注的是过程,分析求解问题的过程,通过调用不同的函数来逐步地解决问题。而C++是一门面向对象的语言,基于C++的考虑方式,大象进冰箱又可以分装为两个对象,每一个对象里面包含了自己的属性和一些方法,如下图:
C++是基于面向对象的,关注的是对象,将一件事情拆分成为不同的对象,由不同对象之间的交互来完成这件事。
二、类的引入
在C语言中,我们能通过结构体来包装一系列的变量,在C++中结构体的功能得到了一定的强化,在结构体中不仅可以定义变量,还能定义函数。如下面一串代码:
struct Student
{
int num;
char name[20];
void showMessage()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
};
int main()
{
//在C++中定义了一个结构体之后,使用其创建变量就不需要再加struct了,也不需要用typedef来重命名
Student a = { 18,"xiaoming" };
//在调用结构体内部的函数时,方法同调用普通变量一样即可
a.showMessage();
return 0;
}
但是在C++中,引入了class类的概念,我们更喜欢用class来进行操作。
三、类的定义
class Name
{
//类体:由成员变量和成员函数组成
};//分号是不可以省略的
1.基本了解
class是定义一个类的关键字,Name为类的名字,你可以对其任意命名,但是最好是具有一定的实意,{}中是类的主体,类定义完后的分号不可以省略。
类体中的内容称为类的成员:其中的变量称为成员变量或者类的属性;其中的函数称为成员函数或者是类的方法。
2.定义方式
(1)声明与定义全部放在内体中,但是需要注意的是:成员函数如果在类中定义,编译器可能会将其处理为内联(inline)函数,例如:
class Student
{
int num;
char name[20];
void showMessage()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
};
(2)类声明之后,类中函数在类外定义,此时需要加上::域限定作用符,以表示其是该类中的函数,例如:
class Student
{
int num;
char name[20];
void showMessage();
};
void Student::showMessage()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
在一般的情况下,我们更推荐采用第二种做法,便于适应日后工程化编程的需求。
四、类的访问
在上面的代码中,如果你想定义一个Student类的变量,并给它赋初值,在VS的环境下,你会发现还没有编译,而编译器就已经给你报红了。这是为什么呢?因为在class类中,成员默认是私有的,不能被外界访问,如果你需要访问它当中的成员,则需要使用类的访问限定符对其进行封装。
int main()
{
//error
Student a = { 18,"xiaoming" };
a.showMessage();
return 0;
}
类的访问限定符有三个:public(公有)、protected(保护)、private(私有)
- public覆盖的成员允许被外界访问
- protected和private覆盖的成员在类外不能被直接访问(在目前阶段,二者类似)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
- 如果没有再出现访问限定符,作用域到 } 就结束
- class的默认访问操作权限是private,而struct的默认访问权限是public(因为C++兼容了C),这也就说明在struct中也可以使用访问限定符来设置访问的权限,操作与class的类似。
面向对象的三大特性:封装、继承、多态。
目前,在类和对象的阶段主要研究的是封装特性。封装是什么?封装是将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开的接口和对象进行交互。本质是一种管理,让用户更容易使用类。
五、类的作用域
类定义了一个全新的作用域,类的所有成员都在类中,在类外定义类中的成员时,需要使用::作用域操作符来指明成员属于哪个域——这一点与namespace命名空间是类似的。
class Student
{
private:
int num;
char name[20];
public:
void showMessage();
void Init();
};
void Student::showMessage()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
void Student::Init()
{
num = 1;
strcpy(name,"xiaoming");
}
int main()
{
Student a;
a.Init();
a.showMessage();
return 0;
}
六、类的实例化
在C语言中,我们使用结构体创建变量,在C++中,我们使用类来创建变量,该情况下的变量我们习惯于称为“对象”。用类来创建对象的过程,被我们称为类的实例化。
- 类是对对象进行描述的,是一个模型一般的东西,限定了类中有哪些成员,定义处一个类都是并没有被分配实际的内存空间来存储。
- 一个类作为一个模版,可以实例化多个对象,实例化出来的对象占据实际的物理空间用来存储类中的成员变量(也只是成员变量哦,后续会提到)。
- 同一个类创建的对象具有相同类型的成员变量,访问时只需要对象名.属性名就可以了。
int main()
{
Student.num=3;//error 语法错误 因为Student是一个类,本身没有空间
return 0;
}
七、类对象模型
1.计算类对象的大小
class Student
{
private:
int num;
char name[20];
public:
void showMessage();
void Init();
};
void Student::showMessage()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
void Student::Init()
{
num = 1;
strcpy(name,"xiaoming");
}
int main()
{
Student a;
cout<< sizeof(a) << endl;
return 0;
}
请猜测控制台的打印结果。
运行上面的一串代码 ,可以看到控制台打印了一个24,可是我不是在类中定义了函数的吗?函数应该是占有内存的啊,在x86的环境下函数指针的大小是4个字节,在x64的环境下是8个字节,这内存空间大小不对啊!
现在请思考一个问题:如果我利用该类创建了多个对象,然后都调用了函数,可是函数的功能却都是相同的,在不同的对象中都有这个函数是否会造成内存的浪费?
所以,在类中的成员函数是没有算在对象中的内存里面的。它们存储在一个公共的代码段中,可是我在调用时并没有传入指针或者引用那些东西啊,这是如何对我对象中的数据进行的操作呢?别急,稍后揭晓~我们先来看三道小题题:
class A1
{
public:
void f1()
{
}
private:
int a1;
};
class A2
{
public:
void f2()
{
}
};
class A3
{
};
请思考这串代码中sizeof(A1)、sizeof(A2)、sizeof(A3)的大小……
答案是:4 1 1
结论:一个类的大小,实际就是该类中成员变量的内存大小之和,当然要注意内存对齐。如果一个类不包含成员变量,编译器则会给其分配一个字节来进行标识。
2.结构体的内存对齐
- 第一个成员在与结构体变量偏移量为0的地址处
- 从第二个成员开始,以后每个成员都要对齐到某个对齐数的整数倍位置
- 这个对齐数是自身成员大小和默认成员对齐数的较小值 备注:vs环境下默认成员对齐数是8
- 当成员全部放入后,结构体的总大小必须是所有成员对齐数中最大对齐数的整数倍
- 如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍处。整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含嵌套结构体的对齐数。
八、this指针
1.引子
在“七.1”中留的悬念就是这个this指针了,this指针是类可以复用性好的基础。
C++编译器给每一个非静态的成员函数都添加了一个隐形的指针参数,让该指针指向当前的对象(函数进行调用时调用该函数的对象),在函数体中所有的“成员变量”的操作其实都是通过this指针去操作的。只不过该this指针对于用户的操作来说是透明不可见的,即用户不需要传递对象的地址,也不需要在函数中添加该类的指针(这会导致报错),编译器来帮你完成。
2.this指针的特性
- this指针的类型:类名*const,即在成员函数中,无法给this指针赋值
- this指针只能在成员函数的内部使用
- this指针本质上是“成员函数”的形参,当对象调用了成员函数时,编译器会将对象的地址传递给this形参。因此对象中并没有存储this指针
- this指针是“成员函数”第一个隐含的参数,一般有编译器通过ecx寄存器自动传递,不需要用户进行手动传递
//可以这样书写
void Student::showMessage()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
}
//不能这样书写,请注意下面的函数参数中const Student*this不能存在,编译器会报错
void Student::showMessage(Student const* this)
{
cout << "num:" << this->num << endl;
cout << "name:" << this->name << endl;
}
//修改之后才可以哦
void Student::showMessage()
{
cout << "num:" << this->num << endl;
cout << "name:" << this->name << endl;
}
在下一节中,我将会讲述类的默认构造函数相关的内容。