本篇主要围绕以下来写内容:
1.面向过程和面向对象初步认识
2.类的引入
3.类的定义
4.类的访问限定符及封装
5.类的作用域
6.类的实例化
7.类的对象大小的计算
8.类成员函数的this指针
目录
示例:C++ 兼容c语言struct的用法 , 但是同时升级成了类
一、面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
二、类的引入
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。
比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数。
示例:C++ 兼容c语言struct的用法 , 但是同时升级成了类
struct:
Stack.h
#pragma once #include<iostream> struct Stack { //private: public: // 成员变量 int* a; int top; int capacity; public: // 成员函数 void Init(int n = 4); void Push(int x); };
Stack.cpp
#include"Stack.h" void Stack::Init(int n) { a = (int*)malloc(sizeof(int) * n); if (nullptr == a) { perror("malloc fail"); return; } capacity = n; top = 0; } void Stack::Push(int x) { //... a[top++] = x; }
Test.cpp
#include"Stack.h" // C++ 兼容c struct的用法 // 但是同时升级成了类 using namespace std; int main() { struct Stack st1; st2.top = 0; st2.Init(); st2.Push(1); st2.Push(2); st2.Push(3); return 0; }
可以在c++中正常运行。
当这里不写struct,也是正确的
三、class:类的定义
只用将struct改成class
一定要注意后面的分号;
类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
类本身就是域(在声明和定义分离时)
c++中struct与class区别
struct 的默认访问权限public
class 的默认访问权限private
默认访问权限:不加public、protected、private时的权限
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类 默认访问权限是private。
注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。
四、类的访问限定符及封装
访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用。
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符。
封装
面向对象三大特性:封装、继承、多态
在类和对象阶段主要研究封装
封装的本质是一种管控
1、cpp数据和方法都放到类里
2、cpp访问限定符去对成员进行限制,想给你访问的是公有,不想给你访问的是私有或者保护
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。 封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
五、类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
class Person { public: void PrintPersonInfo(); private: char _name[20]; char _gender[3]; int _age; }; // 这里需要指定PrintPersonInfo是属于Person这个类域 void Person::PrintPersonInfo() { cout << _name << " "<< _gender << " " << _age << endl; }
六、类的实例化
示例:
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()
{
//class Date d0;
Date d1; // 定义,对象实例化
//Date::_year++; // 错误:因为他只能作为声明
d1.Init(2024, 1, 24);
d1._year++;
//d1._day++;
d1.Print();
return 0;
}
用类的类型创建对象的过程,称为类的实例化
1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;
比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。 谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊
2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
七、类对象模型
a、如何计算类对象的大小
class A1 {
public:
void f1() {}
private:
int _a;
};
class A2 {
public:
void f2()
{
cout << "void f2()" << endl;
}
};
class A3
{};
int main()
{
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
试想一下这个程序的输出结果会是什么?
是不是会在思考成员函数的大小时感到疑惑,由此我们需要先知道,类对象的存储方式。
b、类对象存储方式
sizeof(A1) : ___4___ sizeof(A2) : ___1___ sizeof(A3) : ___1___
没有成员变量的类对象大小1byte,占位,标识对象实例化时,表示定义出来存在过。
结论:
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
c、结构体内存对齐规则
同c语言:
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
那么计算上文类的实例化举例:d1的大小?12 每个int 4byte,三个int ,4*3 = 12,
【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
#pragma pack(1)按照1来对齐
八、this指针
a、this指针的引出
思考这道题的答案:
答案是:正常运行
为什么?
b、this指针的特性
this存在哪里?
A.堆 B.栈 C.静态区 D.常量区 E.对象里面
class Date
{
public:
void Init(int year, int month, int day)
{
this->_year = year;
_month = month;
_day = day;
}
void Print()
{
//this = nullptr;
cout << this->_year << "-" << this->_month << "-" << _day << endl;
}
private:
// 声明
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
d1.Init(2024, 1, 27);
Date d2;
d2.Init(2024, 2, 28);
const int i = 0;
int j = 1;
const char* p = "xzxxxxxx";
cout << &i << endl;
cout << &j << endl;
cout << &p << endl;
cout << (void*)p << endl;
return 0;
}
这里复习一下const用法:
const 在*之前,修饰指针所指向的内容(指针所指向的内容不容修改)
const 在*之后,修饰指针本身(指针本身不可改变,指向的内容可以改变)
而this就是:(Date * const this)因此一个类可以有多个对象
- 堆:用于动态内存分配,
this
指针本身并不存储在堆中。 - 栈:函数调用时,参数和局部变量通常存储在栈上。非静态成员函数的
this
指针作为隐式参数传递给成员函数,因此它存在于栈中。 - 静态区:用于存储静态变量和全局变量,
this
指针与这些区域无关。 - 常量区:用于存储字符串常量等,
this
指针与常量区无关。 - 对象里面:
this
指针并不是存储在对象内部,而是指向对象本身
this指针可以为空吗?
下面由三道题来理解
下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
Printf(A)根本不在对象A内,
传this指针需要解引用(*p)吗?
编译器是否会去解引用,在于解引用是否有意义
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。 你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。