【C++】 类与对象 基础篇

📌 博主名称:池茗

欢迎来到池茗的博客☆*: .. o(≧∇≦)o..:*☆

⭐ 数据结构系列个人专栏:

初阶数据结构_池茗的博客-CSDN博客

⭐ C++基础个人专栏:

C++_池茗的博客-CSDN博客

⭐ 不会因为虚度年华而悔恨,也不会因为碌碌无为而羞愧
📖 点击展开/收起 文章目录

文章目录

下面是C++文档地址,方便大家查阅C++有关使用及语法知识,里面也有论坛,大家自行取用

C++文档查看传送阵

欢迎各位来到池茗的创意工坊:

今天我们要讲的是类与对象

今天内容对于初学C++的小伙伴较为抽象,新概念比较多,不过我会尽我所能讲清楚,这部分类容对于C++学习比较重哟,希望大家能够从中能够有所收获哟, 喵~~

本节知识较为简单,如有知识讲解不对的地方,敬请指正哟,喵~~

类的定义

首先定义一个类有两种方式可以用两个关键字,class和struct,这里面struct大家很熟习,在学习C语言时他作为结构体,被大家使用在这里又要变成类,作为他的第二种使用方法

C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。

cpp 复制代码
struct Date
{
public:
	void DatePrint(int _year, int _month, int _day)
	{
		cout << _year <<"/" << _month <<"/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
cpp 复制代码
class Date
{
public:
	void DatePrint(int _year, int _month, int _day)
	{
		cout << _year <<"/" << _month <<"/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

看上面代码,这是量的简单的类,Date就是类的名称,private下面叫做成员,public下面函数叫做成员函数,这样就简单定义出了一个类

public是什么意思? private又是什么意思?

public,private还有后面要学的protected叫做访问限定符

在回答这两个问题之前,先讲讲class和struct的区别>:class默认是private属性,struct默认是public属性

在C++中我们有个很重要的思想叫做封装,这里的public和private就是为了这种封装做准备,在一个类中public,private是可以定义多

个的public到第一个遇到的private之间都是可以被外界访问的 不管是成员还是成员函数,我们把它叫做公开成员,或者叫做公开成员

函数,private到第一个遇到的public之间时私有的,也就是不可被外界访问

为什么要这样做?

因为我们定义出来一个类,里面实现类某些功能,我们只要别人调用这些功能,不要他们碰我们的私有成员,与外界耦合度不高,外界调

用不会很大影响我定义的这个模块,他只有功能的使用权没有功能的修改权 ,你想在一个大工程里面,你要是外界可以随便调用
我内部私有成员,随便修改我的功能,我代码不就可能会崩吗?

类域

cpp 复制代码
class Stack
{ 
public:
// 成员函数
void Init(int n = 4);
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
} c
apacity = n;
top = 0;
}

在这里类似于上一期讲解的命名域,你可以在这里用域作用访问符来访问类内部声明的函数void Stack::Init(int n)

什么时候在类外面定义成员函数,什么时候在类里面定义成员函数?

这就不得不提及上期提到的inline (内联函数),在类里面,小而短的成员函数会默认变成内联函数以此来减少调用函数栈帧,减少消耗 ,

inline函数就算你加上了,编译器他也会有自己的判断,如果你函数过于大,一就不会在main函数中直接展开

inline 关键字只是对编译器的一个"建议",编译器有权忽略它

在类里面也是一致的规则,所以总结一下,调用频次高小的函数放类里面,大而调用频次低的函数声明与函数定义分离

实例化

类似于结构体,定义一个对象,访问方式也与结构体相似

下面展示实例化一个对象,以及调用成员函数

Date2 d1;//定义一个对象

d1.DatePrint();

对象大小计算

那对象大小怎么算?要不要加成员函数

不加成员函数,成员函数默认是inline是直接展开的

"大小"这个属性是针对于变量(对象)的,函数不是变量,所以函数本身没有"大小"。

那怎么算一个对象大小;和算结构体大小差不多,要用到内存对齐

对齐规则:

  1. 第一个成员从0开始对齐,
  2. 每个成员起始位置要是自身对齐数的整数倍
  3. VS默认对齐数为8
  4. 成员的对齐数=min(默认对齐数,自己类型大小)
  5. 对象的大小是最大对齐数的整数倍,最大对齐数=min(最大成员,默认对对齐数)

this指针

Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,当你多个对象调用这个成员函数,编译器咋知道你是那个在调用

编译器会在第一个形参位置偷偷放一个指针,这个指针就是this

void initdate(int year, int month, int day)我们看到的

void initdate(const date2*this ,int year, int month, int day)实际上的

该形参位置无法更改,有了这个指针,每次对象调用成员函数,编译器都会偷偷传入指向该对象的指针,这样就能做到区分是谁在调用此函数

复制代码
date2 d2;
d2.dateprint();//等价于d2.dateprint(&d2);
cpp 复制代码
class Date
{
public:
	//void initdate(const date2*this ,int year, int month, int day)
	void initdate(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	//void dateprint(const date2*this)
	void dateprint()
	{
		//cout << _year << "/" << _month << "/" << _day << endl;
		cout << this->_year << "/" <<this-> _month << "/" <<this-> _day << endl;
	}
	private:
   //注意这里并不是真正的赋值而是告诉电脑,只要我定义一个对象,
   //它里面的这些私有成员默认给我给的值,更像是一种声明叫做缺省值
	//为了区分成员与函数的形参,一般在前面加个"_"
	int _year=0;
	int _month=0;
	int _day=0;
};
int main()
{
   Date d1;
   Date d2;
   d1.initdate(0,0,0);
   d2.initdate(0,0,0);
}

c++规定不能在形参和实参位置显式写this指针但可以在函数体内部调用

cpp 复制代码
class Date {
private:
    int _year;
public:
    // 错误写法(编译器报错):
    // void setYear(Date* this, int year) { ... } 
    // 正确写法:
    void setYear(int year) {
        // 在函数体内部,可以显式使用 this 来访问成员
        this->_year = year; 
        // 也可以不写 this,编译器默认帮你加了
        // _year = year; 
    }
};
int main() {
    Date d;
    // 错误写法(编译器报错):
    // d.setYear(&d, 2026); 

    // 正确调用(编译器会自动帮你传地址):
    d.setYear(2026); 
}

this在类里面是可以显式调用的

调用成员时编译器本身会自动调用this,你可以直接写_year,你也可以显示写出来 this->_year ,

cout << _year << "/" << _month << "/" << _day << endl;

cout << this->_year << "/" << this-> _month << "/" << this-> _day << endl;

假如你型返回this指针也可以直接return this;

this指针的存储位置

this存储在栈区少数在寄存器中间

经典问题(关于this指针空引用问题)

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

下面不会运行崩溃 原因:虽然这里p为空指针,没有发生解引用

A1* p;

p=nullptr;

p->Print();//虽然有->符号但是在汇编上他只是传给了成员函数相当于this

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

会运行崩溃

A2* p2;

p2=nullptr;

p2->Print();//在成员函数中间有_a,相当于this->_a解引用了

总结:

成员函数调用类似于全局函数调用,函数地址在编译时即确定,与对象指针是否为空无关。

A1没有访问成员变量 A2访问了成员变量

  1. 不崩溃的原因:若函数体未涉及对成员变量的访问,就不会发生 this 指针的解引用。
  2. 崩溃的原因:一旦函数体内部访问了成员变量,编译器会自动通过 this 指针进行寻址。
    如果 this 为 nullptr,便会触发经典的"空指针解引用"错误。
    C语言版本实现栈
c 复制代码
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
void STInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
} 
void STPush(ST* ps, STDataType x)
{
	assert(ps);
// 满了, 扩容
		if (ps->top == ps->capacity)
		{
			int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
			STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			} p
				s->a = tmp;
			ps->capacity = newcapacity;
		} 
	ps->a[ps->top] = x;
		ps->top++;
}
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
} 
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
} 
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
} 
int STSize(ST* ps)
{
	assert(ps);
return ps->top;
} 

C++版本实现栈

cpp 复制代码
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
	public :
	// 成员函数
	void Init(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		} _
			capacity = n;
		_top = 0;
	}
	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
				sizeof(STDataType));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		} _
			a[_top++] = x;
	} 
	void Pop()
	{
		assert(_top > 0);
		--_top;
	} bool Empty()
	{
		return _top == 0;
	} 
	int Top()
	{
		assert(_top > 0);
		return _a[_top - 1];
	} 
	void Destroy()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
// 成员变量
	STDataType * _a;
	size_t _capacity;
	size_t _top;
};

下期内容预告:我会讲解构造函数,析构函数,拷贝构造

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

相关推荐
稷下元歌1 小时前
7天学会plc加机器视觉关于运动控制部份,配套视频在bib
开发语言·c++·git·vscode·python·docker·pip
晚笙coding1 小时前
从零讲透 LangChain 输出格式化:让模型真的“能用”
java·开发语言·langchain
奋斗的小方1 小时前
Java进阶篇1-1:异常
java·开发语言·python
A_humble_scholar1 小时前
Linux(三)深入理解 Makefile:自动变量、增量编译原理与文件时间属性
linux·服务器·c++·makefile
sycmancia1 小时前
Qt——多页面切换组件
开发语言·qt
思麟呀1 小时前
C++11并发编程:条件变量
java·linux·jvm·c++·windows
Full Stack Developme1 小时前
Hutool CollUtil 教程
java·开发语言·windows·python
落羽的落羽1 小时前
【项目】JsonRpc框架——开发实现2(业务层)
linux·数据结构·c++·人工智能·算法·json·动态规划
Shadow(⊙o⊙)1 小时前
mkfifo()命名管道-FIFO客户端 服务端模拟。*System V消息队列、信号量(信号灯)。
linux·运维·服务器·开发语言·c++