面向过程和面向对象的初步认识
首先我们先给出以下结论:
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事拆分成不同的对象,靠对象之间的交互完成。
举例说明面向过程和面向对象的区别:
图书馆借书系统
面向过程:
在面向过程的设计中,我们关注的是整个借书流程中的各个步骤和流程。
-
用户借书:用户到图书馆柜台,向图书管理员提出借书请求,管理员检查用户身份和借书资格,然后帮助用户办理借书手续。这个过程可能包括用户身份验证、书籍可用性检查、记录借书信息等。
-
图书归还:用户在借书期限到期后,将书籍归还给图书馆。管理员接收归还的书籍,检查书籍的完整性和归还日期,并在系统中更新借阅记录。
面向对象:
在面向对象的设计中,我们关注的是图书馆中的各种对象及其之间的关系。
-
客户对象:代表借书的用户。客户对象可能有属性如姓名、学号、借书历史等,以及方法如借书、还书等。
-
图书馆对象:代表整个图书馆系统。它可能有属性如图书馆藏书目、借书规则等,以及方法如借书、还书、管理书目等。
-
书籍对象:代表图书馆中的一本书。书籍对象可能有属性如书名、作者、出版日期等,以及方法如检查是否可借、记录借阅信息等。
在面向对象的设计中,我们可以更加直观地理解图书馆系统中的各种对象之间的关系,比如客户通过调用图书馆对象的方法来借书,图书馆对象调用书籍对象的方法来更新借阅记录等。
类的引入
struct Test
{
//成员变量
int a;
double b;
//成员函数
int Add(int x, int y)
{
return x + y;
}
};
但上面结构体的定义,在C++中更喜欢用class来代替。
类的定义
class className
{
//类体:由成员变量和成员函数组成
}; //注意后面的分号
其中class为定义类的关键字,className为类的名字,{}中为类的主体,注意定义结束时加上后面的分号。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量,类中的函数称为类的方法或者成员函数。
类的两种定义方式:
1、声明和定义全部放在类体中。需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2、声明放在头文件(.h)中,定义放在源文件(.cpp)中。
注意:一般情况下,更期望采用第二种方式。
C++实现封装的方式:用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限,选择性的将其接口提供给外部的用户使用。
【访问限定符说明】
1、public修饰的成员可以在类外直接被访问。
2、protected和private修饰的成员在类外不能直接被访问。
3、访问权限从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
4、class的默认访问权限为private,struct为public(因为struct要兼容C)。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
面试题:C++中的struct和class的区别是什么?
answer:C++需要兼容C语言,所以C++中的struct可以当成结构体去使用。此外,C++中的struct还可以用来定义类,和class定义类是一样的,区别是struct的成员默认访问权限是public,而class的成员默认访问权限是private。
类的封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用“::”作用域解析符指明成员属于哪个类域。
class Person
{
public:
//显示基本信息
void ShowInfo();
private:
char* _name; //姓名
char* _sex; //性别
int _age; //年龄
};
//这里需要指定ShowInfo是属于Person这个类域
void Person::ShowInfo()
{
cout << _name << "-" << _sex << "-" << _age << endl;
}
类的实例化
用类类型创建对象的过程,称为类的实例化。
1、类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
就像C语言中定义了一个结构体一样,当你还未用该自定义类型创建变量时,定义结构体类型这个过程并没有分配实际的内存空间来存储它。
2、一个类可以实例化出多个对象,实例化出的对象将占用实际的物理空间来存储类成员变量。
就像你在C语言中定义了一个结构体,然后用该自定义类型创建了一个变量,那么这个变量将占用实际的物理空间来存储其成员变量。
3、类实例化出对象就像现实中使用建筑设计图建造出房子,类就是设计图。设计图只设计出需要什么东西,但是并没有实体的建筑存在。同样类也只是一个设计,只有实例化出的对象才能实际存储数据,占用物理空间。
类对象模型
如何计算类对象的大小
一个类当中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?类的大小又是如何计算的呢?
class Person
{
public:
//显示基本信息
void ShowInfo()
{
cout << _name << "-" << _sex << "-" << _age << endl;
}
public:
char* _name; //姓名
char* _sex; //性别
int _age; //年龄
};
类对象的存储方式猜测
对象中包含类的各个成员
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同的代码保存了多次,浪费空间。那么如何解决呢?

只保存成员变量,成员函数存放在公共的代码段
对于上述两种存储方式,计算机是按照哪种方式来存储的,我们可以通过对下面的不同对象分别获取大小来进行分析:
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
通过单目操作符sizeof来获取这三个对象的大小,结果A1的大小为4个字节,A2的大小为1个字节,A3的大小也为1个字节。
结论:一个类的大小,实际就是该类中“成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类(占位)。
结构体内存对齐规则
第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处)
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。
this指针
this指针的引出
在C++中,类的成员函数可以直接访问对象的成员变量和方法。但是,如果在成员函数内部存在局部变量与成员变量同名,或者在一个成员函数中需要返回对象本身,就会产生歧义,编译器无法确定是调用局部变量还是成员变量,或者无法确定是返回局部变量还是成员变量。为了解决这个问题,C++引入了this指针。
this指针的特性
-
指向当前对象:this指针是一个隐式参数,它指向当前对象的地址。在成员函数内部,通过this指针可以访问当前对象的成员变量和方法。
-
隐式使用:this指针在成员函数内部是隐式存在的,不需要显式声明或传递。编译器会自动将当前对象的地址作为this指针传递给成员函数。
-
指针类型:this指针是一个指向类类型的非常量指针。这意味着this指针可以用于修改对象的成员变量,但不能修改this指针所指向的对象。
#include <iostream>
using namespace std;
class MyClass {
private:
int x;
public:
void setX(int x) {
this->x = x; // 使用this指针访问成员变量
}
void printAddress() {
cout << "Address of this: " << this << endl; // 输出this指针的地址
}
};
int main() {
MyClass obj1, obj2;
obj1.setX(5); // 调用成员函数,隐式传递this指针
obj1.printAddress(); // 输出obj1的地址
obj2.printAddress(); // 输出obj2的地址
return 0;
}
在这个示例中,setX()
函数使用了this指针来访问成员变量,而 printAddress()
函数则输出了this指针的地址。注意,每个对象的this指针都指向对象本身的地址,因此在两次调用 printAddress()
函数时,输出的this指针地址是不同的。