目录
1.启语
初识过C++后,话不多说,我们今天来进入类和对象。
2.面向过程和面向对象初步认识
面向过程(Procedural Programming)和面向对象(Object-Oriented Programming, OOP)是两种主要的编程范式。每种范式提供了一种思考问题和解决问题的方法论,它们在如何组织代码、如何思考数据结构和函数(或方法)之间的关系等方面有根本的不同。
在我们学习的语言中C语言是面向过程的,C++是基于面向对象的。
这二者有什么区别呢?我们就拿洗衣服来简单介绍一下。
C语言 :
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
在面向过程的编程范式中,我们更多地关注于执行的过程和步骤,而不是对象之间的交互。使用C语言来实现洗衣服的例子时,我们可以将整个洗衣过程分解为一系列的函数,每个函数完成一个具体的任务。
C++ :
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
总共有四个对象:人、衣服、洗衣粉、洗衣机
整个洗衣过程:人将衣服放进洗衣机、倒入洗衣粉,启动洗衣机,洗衣机就会完成洗衣过程并甩干
整个过程主要是:人、衣服、洗衣粉、洗衣机四个对象交互完成的,人不需要关心洗衣机具体事如何洗衣服的,是如何甩干的
3.类的引入
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数。
C++ 兼容C语言struct的用法,但是同时升级成了类。
#include<iostream>
using namespace std;
struct Stack
{
//成员变量
int* a;
int top;
int capacity;
//成员函数
void Init(int n = 4)//全参函数
{
a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == a)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
void Push(int x)
{
//...
a[top++] = x;
}
};
int main()
{
/*struct Stack st1;*/
Stack st2;
st2.Init();
st2.Init(1);
st2.Init(3);
st2.Init(4);
st2.Init(8);
return 0;
}
上面结构体的定义,在C++中更喜欢用class来代替。
4.类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
一般情况下,更期望采用第二种方式。注意:为了方便演示使用方式一定义类,大家后序工作中尽量使用第二种。
代码规范
// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
void Init(int year)
{
// 这里的year到底是成员变量,还是函数形参?
year = year;
}
private:
int year;
};
这里的函数可读性不是很好,year难以分清是成员变量还是函数形参。
所以一般都建议这样:
class Date
{
public:
void Init(int year)
{
_year = year;
}
private:
int _year;
};
形式不唯一,等我们工作以后看公司的要求灵活变通就行。
5.类的访问限定符及封装
5.1访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用。
访问限定符说明
1. public 修饰的成员在类外可以直接被访问2. protected 和 private 修饰的成员在类外不能直接被访问 ( 此处 protected 和 private 是类似的 )3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class 的默认访问权限为 private , struct 为 public( 因为 struct 要兼容 C )注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
我们来看看:
protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
class的默认访问权限为private,struct为public(因为struct要兼容C)
我们先看一组代码直观了解:
class Date
{
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date s1;
s1.Init(2024, 3, 20);
return 0;
}
我们看见无法访问到Init,就是因为class默认访问权限为private。我们可以将class改为兼容c语言的struct(公有)。
成功运行。
public修饰的成员在类外可以直接被访问
我们在void前面加上public
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
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 s1;
s1.Init(2024, 3, 20);
s1._day++;
return 0;
}
另外的struct和class除了默认访问权限不一样,其他一样,也就是说可以加访问限定符。
另外的没有访问限定符限定的,就是默认的
struct Date
{
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
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 s1;
s1.Init(2024, 3, 20);
s1.Print()
return 0;
}
这里是struct,默认公有。
5.2封装
封装的本质是一种管控:
1.cpp的数据和方法都存在类里面。
2.cpp访问限定符去对成员进行限制,想给你访问是公有,不想给你访问是私有。
【面试题】
面向对象的三大特性: 封装、继承、多态 。在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。封装本质上是一种管理,让用户更方便使用类 。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器, USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是 CPU 、显卡、内存等一些硬件元件。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此 计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可 。在 C++ 语言中实现封装,可以 通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用 。
6.类的作用域
我们准备两个文件Stack.h和Stack.cpp
Stack.h
#pragma once
#include<iostream>
using namespace std;
class Stack
{
//private:
public:
// 成员变量
int* a;
int top;
int capacity;
public:
// 成员函数
void Init(int n = 4);
void Push(int x);
};
class Queue
{
public:
// 成员函数
void Init(int n);
void Push(int x);
};
一般我们h头文件中只有定义,变量设为私有,函数设为公有。
Stack.cpp
#include"Stack.h"
void Init(int n)
{
a = (int*)malloc(sizeof(int) * n);
if (nullptr == a)
{
perror("malloc");
return;
}
capacity = n;
top = 0;
}
void Push(int x)
{
//...
a[top++] = x;
}
然后就发现出问题了......
难道是private的问题?还记得上面我们讲private的时候吗?
编译器会说出是private的问题,这里明显没有,这是为什么呢?
这时就要提出我们之前初识C++搁置的类域了,类域也是一个域,它也可以解决重名问题。
那他就会像我们之前说的编译器原则
搜索局部和全局了,这时候我们只要给函数加上指定域就可以了,就像这样
7.类的实例化
用类类型创建对象的过程,称为类的实例化
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::_year++;
return 0;
}
我们查看这段代码,成员是声明还是定义?编译器上定义是否在编译器里面开空间。我们调用一下里面的成员,看看是否成立就能知道是声明还是定义。
无法找到,说明没有开空间,所以是声明。
因此,我们要先定义,将对象实例化才能成功运行。
实例化总结
1. 类是对对象进行描述的 ,是一个 模型 一样的东西,限定了类有哪些成员,定义出一个类 并没有分配实际的内存空间 来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个 类,来描述具体学生信息。 类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。 谜语:" 年纪不大,胡子一把,主人来了,就喊妈妈 " 谜底:山羊
2. 一个类可以实例化出多个对象 ,实例化出的对象 占用实际的物理空间,存储类成员变量Person 类是没有空间的,只有 Person 类实例化出的对象才有具体的年龄。
int main()
{Person . _age = 100 ; // 编译失败: error C2059: 语法错误 :“.”return 0 ;}
3. 做个比方。 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设 计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象 才能实际存储数据,占用物理空间。
![](https://img-blog.csdnimg.cn/direct/72028d62492c4db0ba39b1fb3e997878.png)
8. 类对象的模型
我们用一段代码来了解它的模型
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._year;
d2._year;
d1.Print();
d2.Print();
return 0;
}
d1._year和d2._year地址不一样,
我们主要看成员函数地址,我们查看汇编,发现地址一样。
为什么会这样呢?我们看一幅图来了解一下。
答案显然易见,是为了省空间。
我们加上sizeof来看看类的成员函数是否算内存,如果是就是12字节,加上就是20字节,我们看一下。
答案是12字节,那成员函数就不算在内。
那么我们又有了新的问题,如果里面没有成员变量,或者只有成员函数,存储大小是多少呢?
class A2 {
public:
void f2() {}
};
class A3
{};
int main()
{
Date d1;
Date d2;
d1._year;
d2._year;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
我们发现答案为1! 那么为什么要为1呢?
答案是为了占位,标识对象实例化时,定义出来存在过。
因此,没有成员变量的类对象大小1byte。
8.1结构体内存对齐规则
这里和C语言一样,先简单介绍一下,我后面有空会补上C语言详细的(大概)。
1. 第一个成员在与结构体偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS 中默认的对齐数为 8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
【面试题】1. 结构体怎么对齐? 为什么要进行内存对齐?2. 如何让结构体按照指定的对齐参数进行对齐?能否按照 3 、 4 、 5 即任意字节对齐?3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
先用下列代码简单介绍一下
#include<iostream>
using namespace std;
struct A
{
int a;
char i;
};
struct B
{
int a;
char i;
};
int main()
{
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
return 0;
}
这两个内存空间都一样,那么有什么区别吗?这就要介绍一下我们的内存对齐了。
我们发现有三个字节的空间是浪费的,如果没有好处,编译器怎么会用内存对齐呢?
其实和硬件上的规定有关系,只能在整数倍上读。一次只能读4或8位,我们来看看下图:、
内存对齐:
第一次读四位,舍去3位后得到char,第二次读完int,一共两次读完。
内存未对齐:
第一次读四位,舍去int的3位,第二次读到int的一位,第三次读舍去的int3位和第二次的int那一位。一共要三次。
结语
好了今天的类和对象就到这里,我们下次见。