【C++】类和对象(上)

目录

[一. 面向过程、面向对象的初步认识](#一. 面向过程、面向对象的初步认识)

[二. 类的引入](#二. 类的引入)

[三. 类的定义](#三. 类的定义)

[1. 类的2种定义方式](#1. 类的2种定义方式)

[2. 成员变量命名规则的建议](#2. 成员变量命名规则的建议)

[四. 类的访问限定符、封装](#四. 类的访问限定符、封装)

[1. 访问限定符](#1. 访问限定符)

[2. 封装](#2. 封装)

[2.1 C++的封装](#2.1 C++的封装)

[五. 类的作用域](#五. 类的作用域)

[六. 类的实例化](#六. 类的实例化)

[1. 形象化](#1. 形象化)

[七. 类对象模型](#七. 类对象模型)

[八. this指针](#八. this指针)

[1. 引入](#1. 引入)

[2. this 指针的特性](#2. this 指针的特性)

面试题

[3. C / C++ 实现 Stack 对比](#3. C / C++ 实现 Stack 对比)


本篇文章主要构建类和对象的基本框架

一. 面向过程、面向对象的初步认识

C语言是面向过程 的,关注 的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题

C++是基于面向对象 的,关注 的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成

eg:外卖系统

面向过程:上架 - 点餐 - 派单 - 送餐(过程步骤)

面向对象:商家、骑手、用户(对象和对象之间的关系和交互)

现实世界的类和对象映射到虚拟计算机系统

二. 类的引入

C++ 兼容 C语言。struct 以前的用法都可以继续用。同时 struct 升级成了类

C语言中,结构体中只能定义变量。C++中,结构体内不仅可以定义变量,也可以定义函数

cpp 复制代码
#include <iostream>
using namespace std;

class Queue // 类域
{
	void Init()
	{	}
};

// struct Stack
class Stack
{
public:
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			perror("malloc申请空间失败");
			return;
		}
			
		capacity = defaultCapacity;
		top = 0;
	}

	void Push(int x)
	{
		// 检查扩容
		a[top++] = x;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		capacity = top = 0;
	}

	// ...

private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	struct Stack st1;
	st1.Init(20);

	Stack st2;
	st2.Init();
	st2.Push(1);
	st2.Push(2);
	st2.Push(3);
	st2.Push(4);
	st2.Destroy();

	return 0;
}

三. 类的定义

cpp 复制代码
class className
{
 // 类体:由成员函数和成员变量组成
 
};  // 一定要注意后面的分号

class 为定义类的关键字 ,className为类的名字,{ }中为类的主体。; 不能省略

类体中内容称为类的成员:类中的变量称为 类的属性、成员变量;类中的函数称为 类的方法、成员函数

1. 类的2种定义方式

(1)声明和定义分离

类声明放在 .h 成员函数定义 .cpp 注意:成员函数名前要指定类域,类名::

Func.h

cpp 复制代码
class Stack
{
public:
	void Init(int defaultCapacity = 4);

    void Push(int x)
    {
    	// 检查扩容
    	a[top++] = x;
    }
    
    void Destroy()
    {
    	free(a);
    	a = nullptr;
    	capacity = top = 0;
    }

    // ...

private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

Func.cpp

cpp 复制代码
void Stack::Init(int defaultCapacity = 4) // 一定要指定类域
{
    a = (int*)malloc(sizeof(int) * defaultCapacity);
    if (nullptr == a)
    {
        perror("malloc申请空间失败");
        return;
    }
        
    capacity = defaultCapacity;
    top = 0;
}

(2)声明和定义全部放在类体中

类里面定义的函数默认就是inline。具体认不认为是inline,看编译器。

inline 的声明、定义不能分离!!!

C++,长的函数喜欢声明、定义分离。短的函数就直接在类里定义

2. 成员变量命名规则的建议

eg:日期类

cpp 复制代码
class Date
{
public:
	void Init(int year)
	{
		// 这里的year到底是成员变量,还是函数形参?
		year = year;
	}

private:
	int year;
	int month;
	int day;
};

可以编译通过。局部域、类域都有 year,先用局部域。

cpp 复制代码
class Date
{
public:
	void Init(int year)
	{
		_year = year;
	}

private:
	int _year;
	int _month;
	int _day;
};

区分,具体看企业要求

四. 类的访问限定符、封装

1. 访问限定符

public(公有) protected(保护) private(私有)

说明:

  1. public 修饰的成员在类外可以直接被访问
  2. protected、private 修饰的成员在类外不能直接被访问(暂且认为 protected、private 是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. class 的默认访问权限为 private,struct 为 public(因为 struct 要兼容C)

2. 封装

面向对象的三大特性:封装、继承、多态

封装:将 数据 和 操作数据的方法 放在一起。不想给你看的就变成私有,想给你看的就变成公有

封装本质上是**为了更好的管理,只能按规则来,不想让你做...就限制你。**杜绝了C语言中的不文明行为

封装的第一步:把它围起来,放在一起

C语言:数据和方法分离。没有封装,很自由,想进去就进去,不进去也能实现

Stack.c

cpp 复制代码
STDataType STTop(ST* ps)
{
    assert(ps);
    assert(!STEmpty(ps));
    return ps->a[ps->top - 1]; // Top 这个函数只有一行
}

Test.c(正常写法)

cpp 复制代码
int main()
{
    ST st;
    STInit(&st);
    STPush(&st, 1);
    STPush(&st, 2);
    STPush(&st, 2);
    int top = STTop(&st); // 尽管 Top 这个函数只有一行,我也去调用函数。很规范
    return 0;
}

Test.c(野人写法)

cpp 复制代码
int main()
{
    ST st;
    st.a = NULL; // 一会自己访问
    st.top = st.capacity = 0;
    STPush(&st, 1); // 一会调用函数
    STPush(&st, 2);
    STPush(&st, 2);
    int top = st.a[st.top]; // 随机值
    // 看着函数短,就这么写
    return 0;
}

野人写法报错,他不知道我的栈 top 是指向实际位置的下一个,还不用调用函数,自己访问

其实,也是C语言不规范的原因。太自由了

2.1 C++的封装

C++将数据和方法围起来。C++的栈,类的特性就体现了封装。

Func.h

cpp 复制代码
class Stack
{
public:
	void Init(int defaultCapacity = 4);

    void Push(int x)
    {
    	// 检查扩容
    	a[top++] = x;
    }
    
    void Destroy()
    {
    	free(a);
    	a = nullptr;
    	capacity = top = 0;
    }

    int Top()
    {
        return a[top - 1];
    }

    // ... 

private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

Test.cpp

cpp 复制代码
int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	st.Push(2);
    int top = st.Top();
	return 0;
}

想去自己初始化,像 C语言 那样:st.a[...] ?没门。封装起来了,不想给你访问的放成了私有。敢访问就报错

想只有1条路可走:调 Top函数,不管函数是1行还是多行

五. 类的作用域

类定义了一个新的作用域:类域 在类体外定义成员时,需要使用**::**指明成员属于哪个类域

域的特点:

  1. 在不同的域里,可定义同名变量

  2. 限制访问,编译器有自己的搜索规则:局部域 > 类域 > 全局域 = 命名空间(展开,指定)

4个都影响访问 只有局部域、全局域会影响生命周期

六. 类的实例化

函数:

声明:只有函数名,参数,返回类型

定义:实现

变量:声明、定义真正区别在于开不开空间

声明:不开空间,只是声明了类型

定义:

Func.h

cpp 复制代码
class Stack
{
public:
    void Init(int defaultCapacity = 4); // 声明
    void Push(int x)	{	} // 定义
    void Destroy()	{	} // 定义

private:
    // 成员变量
    int* a;
    int top;
    int capacity;
};

private: 里面的是声明 or 定义? 声明一个类

cpp 复制代码
int main()
{
	Stack st1;
	Stack st2;
    
	return 0;
}

这里的定义很特殊,整体定义,不是一个一个定义
用类类型创建对象的过程 (这个过程)叫:类实例化对象 / 对象定义

定义了,就开空间了。开 st1的 a top capacity、st2 的 a top capacity

1. 形象化

  1. 类描述对象 ,像模型 ,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
  2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量

设计图纸 <==> 类

设计图只有一张,根据设计图能建造出很多栋房子 <==> 用一个类,可以实例化出很多对象

图纸里面不能住人 <==> 类里面不能存数据

盖出来的房子才能住人 <==> 类实例化出的对象,定义出的对象才开了空间,才可以存数据

结构体只是规定了有 xxxx 一些数据,但他并不能存数据 结构体定义出的变量才能存数据

所以不能这样写:(即使是公有的)

Func.h

cpp 复制代码
class Stack
{
public:
    void Init(int defaultCapacity = 4); // 声明
    // ...
// private:
    // 成员变量
    int* a;
    int top;
    int capacity;
};

Test.cpp(错误写法)

cpp 复制代码
int main()
{
	Stack st1;
	Stack::top = 1; // 错	想往图纸里面住人?
    // 类访问 top 是声明。top 不能存数据,没开空间

    st1.top = 1; // 对(假设是公有)因为开了空间
	return 0;
}

七. 类对象模型

对象的大小只算成员变量,不算成员函数。 遵循C语言中的结构体内存对齐
对象中只存储成员变量,没存储成员函数

cpp 复制代码
class A
{
public:
	void PrintA() // 篮球场
	{
		cout << _a << endl;
	}
private:
	char _a; // 卧室
};

int main()
{
	A aa1;
	A aa2;
	cout << sizeof(A) << endl; // sizeof(类),拿图纸算面积
	cout << sizeof(aa1) << endl; // sizeof(对象),具体拿尺子量。二者是一样的
    
	return 0;
}

类对象的存储方式猜测:

1. 对象中包含类的各个成员

2. 代码只保存一份,在对象中保存存放代码的地址

3. 只保存成员变量,成员函数存放在公共的代码段

cpp 复制代码
class A
{
public:
	void PrintA() // 篮球场
	{
		cout << _a << endl;
	}
// private:
	char _a; // 卧室
    int _b; // 厨房
    char _c; // 客厅
};

int main()
{
	A aa1;
    aa1._a = 1; // 到对象里面去找
    aa1.PrintA(); // 不是到对象里面去找,因为 PrintA 就没存在 aa1 这个对象里
    
	return 0;
}

空类 / 类中仅有成员函数

cpp 复制代码
// 类中仅有成员函数
class A2 
{
public:
	void f2() {}
};

// 类中什么都没有---空类
class A3
{};

int main()
{
    // 没有成员变量的类对象,需要1byte,是为了占位,表示对象存在
    // 不存储有效数据
    cout << sizeof(A2) << endl;
    cout << sizeof(A3) << endl;
    A2 aa1;
    A2 aa2;
    cout << &aa1 << endl; // 有地址,说明开空间了
    cout << &aa2 << endl;

    return 0;
}

八. this指针

1. 引入

cpp 复制代码
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; // 定义了2个日期类对象
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print(); // call Print(0x21311111) 同一块地址
	d2.Print(); // call Print(0x21311111)

    return 0;
}

Print 在公共区域里,2个 Print 调的同一个函数,为什么打印的结果不同?

有隐含的 this 指针

cpp 复制代码
void Print()
{
    cout << _year << "-" << _month << "-" << _day << endl;
}

// 编译器会对成员函数处理
void Print(Date* this)
{
    cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
cpp 复制代码
int main()
{
	Date d1, d2; // 定义了2个日期类对象
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	// d1.Print();
	// d2.Print();

    // 调用的地方也会处理
    d1.Print(&d1);
    d2.Print(&d2);

    return 0;
}

以前的栈,怎么访问栈的成员? 把栈的对象的地址传过去

以前是自己做,现在是暗箱操作(自动挡)

调的是同一个函数,但形参不同
d1 调用的时候,this 指向 d1,就访问的是 d1这个具体对象的年月日

自己不能这么写:

cpp 复制代码
void Print(Date* this) // 报错
{
    cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}

d1.Print(&d1); // 报错
d2.Print(&d2); // 报错

编译器规定:不允许在形参、实参的位置显式的传递

但允许在里面用

cpp 复制代码
void Print()
{
    // this = nullptr; 错
    // 不能给改了 Print(Date* const this),修饰指针本身,指向的内容可以被改
    cout << this << endl;
    cout << this->_year << "-" << _month << "-" << _day << endl;
}

2. this 指针的特性

  1. this 指针的类型:类 类型* const即成员函数中,不能给 this 指针赋值。
  2. 只能在"成员函数"的内部使用
  3. this 指针本质上是"成员函数"的形参,当对象调用成员函数时,将对象地址作为实参传递给 this 形参。所以对象中不存储 this 指针
  4. this指针是"成员函数"第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递

面试题

**1.this 指针存在哪里?**对象里、栈、静态区、常量区

this 是形参 ,所以this指针是跟普通参数一样存在函数调用的 里面

成员函数里频繁访问 this,用寄存器优化,寄存器很快

VS 中对 this 指针传递进行优化,对象地址是放在 ecx,ecx 存储 this 指针的值(不是所有编译器都这样)

**2.this 指针可以为空吗?**可以

下面程序的结果:A 编译报错 B 运行崩溃 C 正常运行

cpp 复制代码
class A
{
public:
    void Print()
    {
        cout << "Print()" << endl;
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr;
    p->Print();
    return 0;
}

答案:C 正常运行

肯定不是 A 编译报错:就算有问题也是空指针。空指针访问是运行问题。运行的问题不会报编译的错误
不要以为 p->就是解引用

p 调用 Print,不会发生解引用。因为 Print 的地址不在对象中。编译的时候已经 call(地址)。在公共代码段找到的
p会作为形参传递给 this 指针。p是空指针,传递(copy)空指针不会报错

把 p 给 ecx(传参);call 调用函数。(指令里面没有解引用的行为(没有调用里面的类成员))

this 指针是空,但是函数内没有对 this 指针解引用


cpp 复制代码
class A
{
public:
    void PrintA()
    {
        cout << _a << endl;
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

答案:B 运行崩溃

p 调用 PrintA,不会发生解引用。因为 PrintA 的地址不在对象中。编译的时候已经 call(地址)。在公共代码段找到的
p会作为形参传递给 this 指针。p是空指针,传递(copy)空指针不会报错

this 指针是空,但函数内访问成员变量 _a,本质是 this->_a**(对空指针解引用)**


cpp 复制代码
class A
{ 
public:
    void Print() 
    {
        cout << "Print()" << endl;
    }
private:
    int _a;
};

int main()
{
    A* p = nullptr;
    p->Print();

    A::Print(); // 报错
    return 0;
}

函数不在对象里,为什么不让用类域直接访问?

p 是对象,我就传对象的地址;p 是对象的指针,我就传对象的指针

A :: Print( ) ; 这样写 this 指针传啥?

cpp 复制代码
A::Print(nullptr); // 错
A::Print(p); // 错

this 指针不能在形参和实参显式传递

3. C / C++ 实现 Stack 对比

C++中 Stack * 参数是编译器维护的(自动挡),C语言中需用用户自己维护(手动挡)

C语言没有封装,数据和方法分离。可以用函数访问数据;也可以通过对象(C语言中喜欢叫变量)访问数据

本篇的分享就到这里了,感谢观看 ,如果对你有帮助,别忘了点赞+收藏+关注

小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

相关推荐
用户6869161349042 分钟前
哈希表实现指南:从原理到C++实践
数据结构·c++
大老板a1 小时前
c++五分钟搞定异步处理
c++
羑悻的小杀马特5 小时前
从信息孤岛到智能星云:学习助手编织高校学习生活的全维度互联网络
c++·学习·生活·api
C++ 老炮儿的技术栈6 小时前
VSCode -配置为中文界面
大数据·c语言·c++·ide·vscode·算法·编辑器
90wunch6 小时前
更进一步深入的研究ObRegisterCallBack
c++·windows·安全
刃神太酷啦6 小时前
聚焦 string:C++ 文本处理的核心利器--《Hello C++ Wrold!》(10)--(C/C++)
java·c语言·c++·qt·算法·leetcode·github
DARLING Zero two♡6 小时前
C++数据的输入输出秘境:IO流
c++·stl·io流
知然1 天前
鸿蒙 Native API 的封装库 h2lib_arkbinder
c++·arkts·鸿蒙
十五年专注C++开发1 天前
Qt .pro配置gcc相关命令(三):-W1、-L、-rpath和-rpath-link
linux·运维·c++·qt·cmake·跨平台编译