一、类的定义
1.那么众所周知,C语言是面向过程的,关注的是过程,分析出求解的步骤,通过函数的调用来逐步解决问题
2.而C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间交互完成!
class classname
{
// 类体:由 成员变量 和 成员函数 组成
};// 一定要注意后面的分号
3.class是定义类的关键字,classname是类的名字,{}中的为类的主体,注意:类结束时后面的分号不能省略!
4.类中的内容:称为类的成员:类中的变量称为成员变量或者类的属性。类中的函数称为成员函数或者类的方法。
二、类的访问限定符和封装
1.类的访问限定符
用类 将 对象的属性(变量)和方法(函数)结合在一起,让对象更加完善!
通过访问权限选择性的将类的接口提供给外部用户进行访问使用
C++的访问限定符分为以下几种:
- public:被public修饰的成员在类外可以直接被访问
- protected和private:被protected和private所修饰的成员在类外不能被直接访问!(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为的默认访问权限public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来
定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类
默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大
家介绍。
2.类的封装
面向对象的三大特性:封装、继承、多态
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用
户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日
常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
封装使面向对象更严格、更规范。
三、类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。整个域是一个整体,成员变量无论在成员函数上面或者下面都可以进行访问。
#include<stdlib.h>
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
//初始化
void StackInit(int initSize);
public:
STDataType* a;
int size;
int capacity;
};
//指明成员属于Stack类
void Stack::StackInit(int initSize = 4)
{
a = (STDataType*)malloc(sizeof(STDataType) * 4);
size = 0;
capacity = initSize;
}
四、类的实例化
1.什么是类的实例化
什么是类的实例化?
用 类 创建 对象 的过程!
2.为什么要对类进行实例化?
1.类是对 对象描述的一个东西一个模板,就是一个空壳。这里面只限定了类有哪些成员,定义出了一个类,但是并没有分配实际的空间来储存它。
(就比如说:入学时填的学生信息表,那个学生信息表的表格就是一个类,它就相当于一个空壳)
2.一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
3.具体理解实例化
实例:
#include <stdlib.h>
#include<iostream>
using namespace std;
typedef int STDataType;
struct Stack
{
//初始化
void StackInit(int initSize = 4)
{
a = (STDataType*)malloc(sizeof(STDataType) * 4);
size = 0;
capacity = initSize;
}
STDataType* a;
int size;
int capacity;
};
int main()
{
Stack st; //实例化对象
return 0;
}
4.对定义的深刻理解
注意:对变量定义成功的标志:该变量在内存中开了空间
class Date
{
//private:
public:
int _year;//这里对变量只是声明!!!声明不开辟空间!
int _month;//注意:对变量定义成功的标志:该变量在内存中开了空间
int _day;
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
//Date D1;//此处对类进行了定义!
D1._year;//为啥不能访问?因为:_year没有定义
return 0;
}
五、类对象模型
1.如何计算类的大小
1.遵循内存对齐原则
我们直接通过上实例代码进行理解:
//既有 成员函数 又有 成员变量
class A1
{
public:
void f1(){}
private:
int _a;
};
//只有 成员函数
class A2
{
public:
void f2(){}
};
//空类
class A3
{
};
int main()
{
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
C++规定:没有成员变量的类对象,它的大小为1字节,占位标识类存在过
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意:空类的大小,空类比较特殊,编译器给了空类1个字节来唯一标识这个类的对象。
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值,VS中默认的对齐数为8
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
2.类的对象储存方式
假如对象中包含类的各个成员,既包含成员变量,又包含成员函数,由于每个对象中成员变量是不同的,但调用的是同一份成员函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份成员函数代码,相同代码保存多次,会浪费空间。
因此C++采用类对象中只存储成员变量,成员函数存放在公共代码段中的方式来存储类的对象。
//既有 成员函数 又有 成员变量
class A1
{
public:
void f1(){}
private:
int _a;
};
//只有 成员函数
class A2
{
public:
void f2(){}
};
//空类
class A3
{
};
int main()
{
A1 a1;
A2 a2;
A3 a3;
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
cout << &a1 << endl;
cout << &a2 << endl;
cout << &a3 << endl;
return 0;
}
尽管对象a2的类中没有成员变量仅有成员函数,对象a3的类中既没有成员变量也没有成员函数,但是它们的地址不同,表明对象a2和对象a3存在过。
六、this指针
1.this指针引出
我们先来定义一个日期类 Date:
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, d2;
d1.Init(2022,1,11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
对于上述类,有这样的一个问题:
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
2.this指针的特性
- this指针的类型:类类型 const*,即成员函数中,不能给this指针赋值。(但是可以给指针所指向的内容进行赋值)
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参(存放在栈区),当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
(1)例如:如果定义两个对象,d1和d2如何访问自己的年月日呢?两个对象调用的是同一份成员函数,如何做到各自初始化各自的?
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d1;
Date d2;
d1.Init(2024, 2, 15);
d2.Init(2024, 2, 16);
d1.Print();
d2.Print();
return 0;
}
因为编译器做了处理,Init函数的入参不是3个参数,实际上是4个参数,编译器会增加一个隐含的参数:this指针,编译器会把对象的地址传给this。
int main()
{
Date d1;
d1.Init(2024, 2, 15);//实际为d1.Init(&d1,2024,2,15)
Date d2;
d2.Init(2024, 2, 16);//实际为d2.Init(&d2,2024,2,16)
return 0;
}
编译器增加的隐含的this指针参数,即void Init(Date* this, int year, int month, int day),作为形参,this指针存在于栈中。
3.两个例题充分理解
1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
比特就业课
8.3. C语言和C++实现Stack的对比
1. C语言实现
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
这里变量p是一个类的指针,把它置为nullptr,然后把它当做指针,赋给this指针,在语法上并没有错误所以不会编译出错,然后访问函数Print,但是访问的时候,他并没有对this指针所指向的内容进行解引用(访问)
所以:选C
2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
这里调用了函数,打印a,就着nullptr作为形参传递给this指针,进行解引用,就会运行崩溃!!!
好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!