C++类与对象(上)

news2025/2/9 7:04:19

类与对象(上)

  • 1.面向过程与面向对象初步认识
  • 2.类的引入
  • 3.类的定义
  • 4.类的访问限定符以及封装
    • 4.1访问限定符
    • 4.2 封装
  • 5.类的作用域
  • 6.类的实例化
  • 7.类对象模型
    • 7.1计算类对象的大小
    • 7.2类对象的存储方式的猜想
  • 8.this指针
  • 8.2this指针的特性

1.面向过程与面向对象初步认识

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

比如洗衣服,
C语言的做法,每个地方都需要写一个对应的函数。
在这里插入图片描述

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

也比如洗衣服
在这里插入图片描述

2.类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数

//c语言中,结构体只能定义变量,栈的其他函数只能在结构体外写
struct Stack
{
	int* a;
	int top;
	int capacity;
};

void StackInit(struct Stack* ps)
{
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

void StackPush(struct Stack* ps,int x)
{
	ps->a[ps->top++]=x;
}
//...

int main()
{
	struct Stack st;
	StackInit(&st)
	StackPush(&st,1);
	//..
	return 0;
}

C++对struct进行了升级
1.兼容C中struct的所有用法
2,升级成了类(结构体不仅可以写变量,还可以写函数)

//Stack是类名
struct Stack
{
	//成员函数
	void Init(int n = 10)
	{
		//由于在一个类域中,所有类域中的变量可以直接使用
		a = (int*)malloc(n * sizeof(int));
		if (a == NULL)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = n;
	}
	void Push(int x)
	{
		a[top++] = x;
	}
	void Pop()
	{
		assert(!Empty());
		--top;

	}
	bool Empty()
	{
		return top == 0;
	}
	//.....

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

int main()
{	
	//C的还可以用
	struct Stack ss;


	//C++把这个当成了一个类
	//实例化了一个st的对象
	Stack st;
	//调用类中的函数
	st.Init();
	st.Push(1);
	st.Push(2);

	st.Pop();

	st.Destory();

	return 0;
}

上面结构体的定义,在C++中更喜欢用class来代替。

3.类的定义

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

class为定义类的关键字ClassName为类的名字{}中为类的主体注意类定义结束时后面分号不能省略
类体中内容称为类的成员类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数

类的两种定义方式:

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理
class  Data
{
//public和private是访问限定符,下面解释
public:
	void Init(int year, int month, int data)
	{
		year = year;
		month = month;
		data = data;
	}

private:
	int year;
	int month;
	int data;
};
  1. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
//Stack.h
class Stack
{
public:
	void Init(int n=10);
	void Push(int x);
	void Pop();
	bool Empty();
	void Destroy();


private:
	int* _a;
	int _top;
	int _capacity;
};

//Stack.cpp

//函数中的变量会先在局部变量找,找不到就去类中找,找不到再去全局变量中找,再再找不到就报错
void Stack::Init(int n)
{
	_a = (int*)malloc(n * sizeof(int));
	if (_a == NULL)
	{
		perror("malloc fail");
		return;
	}
	_top = 0;
	_capacity = n;
}
void Stack::Push(int x)
{
	_a[_top++] = x;
}
void Stack::Pop()
{
	assert(!Empty());
	--_top;

}
bool Stack::Empty()
{
	return _top == 0;
}

void Stack::Destroy()
{
	free(_a);
	_a = nullptr;
	_top = _capacity = 0;
}

//test.cpp
int main()
{
	Stack st;
	st.Init();
	st.Push(1);

	st.Destroy();

}

为什么每个函数前面都要加类名::

原因在于,类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。也可以这样想,如果再写一个队列的类,队列也有差不多的函数,那么当我们想看这个函数内部怎么实现的时候,两个Init,Push…就分不清到底是那个类的函数了。

写项目的时候,一般建议选择第二种写法,不然再类中定义函数,就会显得代码很多,不好看。

函数的命名建议

class  Data
{
public:
	void Init(int year, int month, int data)
	{
		year = year;
		month = month;
		data = data;
	}

private:
	int year;
	int month;
	int data;
};


//修改之后
class  Data
{
public:
	void Init(int year, int month, int data)
	{
		_year = year;
		_month = month;
		_data = data;
	}

private:
	int _year;
	int _month;
	int _data;
};

注意最上面这段代码, 初始化函数是不是有些别扭,因为根本分不清是给赋值是类中的变量还是形参;因此C++申请变量一般前面加个_,或者变量后面加个_;

4.类的访问限定符以及封装

4.1访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用.
在这里插入图片描述
访问限定符:限定的是类外面的访问,不限制类里面的访问

【访问限定符说明】

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

问:C++中struct和class的区别是什么?

答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序再说.

4.2 封装

问:面向对象的三大特性

答:封装、继承、多态。

这里主要结束的是封装,继承和多态后面再说,那什么是封装呢?

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

封装本质上是一种管理,让用户更方便使用类。

比如电脑,我们知道电脑有cpu,显卡,内存等一些硬件把这些东西封装起来,然后这么复杂的一件东西,提供给用户是键盘,usb接口等等,我们操作电脑也不会直接操作那些硬件,而是使用提供给我们的这些东西。这实际上也是一种封装。

5.类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

class Person
{
public:
 	void PrintPersonInfo();
private:
 	char _name[20];
 	char _gender[3];
 	int  _age;
};

// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;

}

6.类的实例化

用类类型创建对象的过程,称为类的实例化

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;

做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

class  Data
{
public:
	void Init(int year, int month, int data)
	{
		_year = year;
		_month = month;
		_data = data;
	}

private:
	int _year;
	int _month;
	int _data;
};
int main()
{
	//报错
	Data::_year = 2023;//原因就是只是声明有这个东西,但是没有开空间
	Data::Init()//那这个报错的原因呢?其实是因为this这个指针,下面说
}
  1. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

7.类对象模型

7.1计算类对象的大小

class A
{
public:
	void Print()
	{
		cout << "_a" << endl;
	}
private:
	int _a;
};

上面这个类的大小是多少呢?

分析一下:有一个char类型,还有一个函数,我们再C语言学过计算结构体的大小。因此char很好算,函数大小这么算,因为函数有多条指令,第一条指令就是函数的地址,就是函数的大小,而指针就是地址,在32位下,指针大小为4个字节,再64位下,指针大小为8个字节,假设我们在32位下,那么这个类的大小最终为5字节。

在这里插入图片描述

答案和我们预想的不一样。为啥?
我们发现这个1好像我们char类型的大小,没有计算成员函数的大小。那我们的成员函数放那去了?

7.2类对象的存储方式的猜想

1.对象中包含类的各个成员
在这里插入图片描述

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?

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

在这里插入图片描述

3.只保存成员变量,成员函数存放在公共的代码段在这里插入图片描述
公共代码区也是常量区或代码段

问题:对于上述三种存储方式,那计算机到底是按照那种方式来存储的?

其实第二种和第三种感觉都还可以,但是计算机最终选择的是第三种

假设小区里有一个健身房(成员函数),每户家庭(成员变量),然后去健身房健身
第二种就是,不仅告诉你健身房在哪,还给你健身房密码和健身房地图
第三种就是,直接告诉你健身房在哪里,想去健身自己就去了,不需要地图。

类成员函数是编译器自己放的,并且是编译器自己去拿的,不需要我们自己去找那块空间。

计算下列类的大小

// 类中既有成员变量,又有成员函数
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;
}

注意我们知道类的成员函数放在常量区,即使不太会算,也要猜测A2和A3是一样大小的,

在这里插入图片描述

结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象

问:
1. 结构体怎么对齐? 为什么要进行内存对齐?
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

4. 如何知道结构体中某个成员相对于起始位置的偏移量

关于内存对齐这块知识可以看最详细讲解结构体,枚举,联合,看完就搞懂了结构体部分。

8.this指针

我们定义一个日期类Date

class Date
{
public:
	void Init(int year, int month, int date)
	{
		_year = year;
		_month = month;
		_date = date;
	}
	void Print()
	{
				cout << _year <<" " << _month <<" "<< _date << endl;
	}

private:
	int _year;
	int _month;
	int _date;
};


int main()
{
	Date d1;
	Date d2;
	d1.Init(2023, 3, 12);
	d2.Init(2023, 4, 12);

	d1.Print();
	d2.Print();	
	
	return 0;
}

对于上述类,有这样的一个问题:
在这里插入图片描述
成员函数都存在常量区,是同一个函数,定义两个对象,都是使用了同一个函数,编译器是怎么知道我们初始化的是谁,打印的是谁?

我们在看一下栈是如何知道我们用调用的是谁

struct Stack
{

	int* a;
	int top;
	int capacity;
};

void StackInit(struct Stack* ps)
{
	ps->a = (int*)malloc(10 * sizeof(int));
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	ps->top = 0;
	ps->capacity = 10;
}

void StackPush(struct Stack* ps, int x)
{
	ps->a[ps->top++] = x;
}



int main()
{	
	
	struct Stack st;
	StackInit(&st);
	StackPush(&st, 1);

	return 0;
}

发现没有,在C语言中,我们每次调用栈的函数,都要传st的地址,然后函数就知道我们要使用st。

而C++为了方便,不用我们每次调函数还需要传这个参数,因此引入了this指针解决这个问题。

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

class Date
{
public:
	void Init(Date* const this,int year,int month,int date)
	{
		this->_year = year;
		this->_month = month;
		this->_date = date;
	}

	//void Init(int year, int month, int date)
	//{
	//	_year = year;
	//	_month = month;
	//	_date = date;
	//}
	void Print(Date* const this)
	{
		cout << this->_year <<" " << this->_month <<" "<< this->_date << endl;
	}

private:
	int _year;
	int _month;
	int _date;
};


int main()
{
	Date d1;
	Date d2;
	//Init(&d1,2023,3,12)
	d1.Init(2023, 3, 12);
	//Init(&d2,2023,3,12)
	d2.Init(2023, 4, 12);

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

	return 0;
}

this指针的定义和传递,都是编译器的活,我们不能去抢,但是我们可以在类里面使用this指针,我们加了编译器就不加了。

8.2this指针的特性

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

问:

1. this指针存在哪里?
2. this指针可以为空吗?

答:

1.this是一个形参,当成员函数被调用时,才会被定义,因此存在栈帧中。

2.this指针不能为空,如果在内部把this置为nullptr,就会报运行错误,因为空指针不能解引用。所以this指针类型是,类类型 const。

问:

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	 void Print()
	 {
	 	cout << "Print()" << endl;
	 }
private:
 	int _a;
};

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

// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
	 void PrintA() 
	 {
	      cout<<_a<<endl;
	 }
private:
 	int _a;
};

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

答:

1.C,p->Print(),成员函数在常量区,直接去常量区去找这个函数,不会解引用,然后把p的传过去,因为p本身就是一个地址了。传过去之后直接打印。

2.B,和上面一样,把p传过去,但是传过去之后打印会有解引用。this->_a,对空指针解引用会报运行错误。

感觉作者总结的知识对自己有用的小伙伴,请点赞,评论,收藏哦,让你下次不迷路。将会持续更新。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaWeb之JSP

文章目录 JSP的基本介绍JSP的本质JSP的三种语法JSP头部的page指令language属性contentType属性image.pngpageEncoding属性import属性autoFlush属性 - 给out输出流使用buffer属性 - 给out输出流使用errorPage属性isErrorPage属性session属性extends属性 JSP中的常用脚本声明脚本…

贸易企业缺进项严重,如何减轻13%的增值税税负?

贸易企业缺进项严重&#xff0c;如何减轻13%的增值税税负&#xff1f; 《税筹顾问》专注于园区招商&#xff0c;您的贴身节税小能手&#xff0c;合理合规节税&#xff01; 贸易企业的增值税税负很重&#xff0c;这不仅是因为13%的高额增值税税率&#xff0c;也因为贸易企业缺进…

K8s 部署 Apache Kudu 集群

一、K8s 部署 Apache Kudu 集群 安装规划 组件replicaskudu-master3kudu-tserver3 1. 创建命名空间 vi kudu-ns.yamlapiVersion: v1 kind: Namespace metadata:name: apache-kudulabels:name: apache-kudukubectl apply -f kudu-ns.yaml查看命名空间&#xff1a; kubectl …

傻白入门芯片设计,形式化验证方法学——AveMC工具学习(二十)

一、形式验证方法学 &#xff08;一&#xff09;什么是形式化验证&#xff1f; 形式化验证方法学是使用数学证明的方法&#xff0c;分析设计中所有可能的状态空间来验证设计是否符合预期。形式化验证方法主要有三个方面的应用&#xff1a;定理证明、模型检验和等价性检查。 …

让你不再疑惑语音翻译怎么弄

语音是人类交流的一种最基本的方式&#xff0c;但是当我们需要和来自不同国家或地区的人交流时&#xff0c;语言的限制往往让我们感到无力。然而&#xff0c;如今的语音翻译技术正在以惊人的速度发展&#xff0c;使得我们的声音可以轻松地跨越语言的界限。那么&#xff0c;你知…

强化学习:蒙特卡洛方法(MC)

引入蒙特卡洛方法例子 以抛硬币为例&#xff0c;将结果(正面朝上或反面朝上)表示为作为随机变量 X X X &#xff0c;如果正面朝上则 X 1 X1 X1 &#xff0c;如果反面朝上&#xff0c;则 X − 1 X-1 X−1&#xff0c;现在要计算 E [ X ] E[X] E[X]。    我们通常很容易…

JDK常用的数据类型【1】 ——HashMap(分享篇)

x mod 2^n x & (2^n - 1) 1. 拿到 key 的 hashCode 值 2. 将 hashCode 的高位参与运算&#xff0c;重新计算 hash 值 3. 将计算出来的 hash 值与 (table.length - 1) 进行 & 运算数据结构 1.B树 和 B树 B树叶子节点可以存放多个元素B树的叶子节点之间是有指针的 红…

网络安全|渗透测试入门学习,从零基础入门到精通—收集信息篇

目录 前面的话 1、收集域名信息 1.1、Whois查询 ​编辑1.2、备案信息查询 2、收集敏感信息 3、收集子域名信息 3.1、子域名检测工具 3.2、搜索引擎枚举 3.3、第三方聚合应用枚举 3.4、证书透明度公开日志枚举 本章小结 前面的话 本人喜欢网络完全的一些知识&#xff…

老油条辞职信写好了,00后卷王的自述,我难道真的很卷?

前言 前段时间去面试了一个公司&#xff0c;成功拿到了offer&#xff0c;薪资也从12k涨到了18k&#xff0c;对于工作都还没两年的我来说&#xff0c;还是比较满意的&#xff0c;毕竟一些工作3、4年的可能还没我高。 我可能就是大家说的卷王&#xff0c;感觉自己年轻&#xff0…

出现报错Invalid bound statement (not found): xxx.xxxMapper.方法名 时的几种异常排除方法

报错信息&#xff1a;Invalid bound statement (not found): com.ruoyi.enterpriseman.trade.mapper.TradeEnterpriseMapper.selectTradeEnterpriseList 1.mapper.xml中的namespace和实际的mapper文件不一致 这个问题其实很好解决&#xff0c;瞪大眼睛&#xff0c;仔仔细细看看…

[SSM]Maven详解

目录 Maven 自动化构建工具 Maven简介 Maven的核心概念 maven约定的目录结构 仓库 POM文件 生命周期、命令、插件 Maven在IDEA中的应用 IDEA集成maven IDEA创建Maven版java工程 IDEA创建Maven版web工程 IDEA中导入Maven工程&#xff08;module&#xff09; 依赖管理…

Windows 离线安装mysql5.7

一、下载MySQL5.7最新版 1、官网地址 https://downloads.mysql.com/archives/community/ 2、下载MySQL5.7最新版 下载下图所示的安装包&#xff1a; 二、安装MySQL5.7 1、解压 将刚才下载压缩包解压搭配目录C:\software\mysql-5.7.41&#xff0c;&#xff08;路径大家可…

背完这195道软件测试面试题,帮你轻松拿下提前批offer

前言&#xff1a; 最近在整理字节&#xff0c;阿里&#xff0c;腾讯&#xff0c;京东的面试题&#xff0c;挑了一部分在四个大厂面试题中出现频率比较高的&#xff0c;发现还是基础知识比较多&#xff0c;废话不多说&#xff0c;你们自己看看&#xff0c;这里小编只放了面试题&…

电动超声波硅胶洁面仪单片机开发方案

近来&#xff0c;网红超声波洁面仪受到人们喜爱&#xff0c;特别是爱化妆的女性朋友&#xff0c;常用来清洁脸部肌肤。在本方案中&#xff0c;洁面仪IC采用宇凡微YF单片机&#xff0c;我们提供多种洁面仪方案&#xff0c;根据不同功能需求有多个洁面仪芯片可供选择。 一、超声波…

价值5k的软件测试企业级实战项目,只为了回答你软件测试如何学!

学习软件测试如何学&#xff0c;在回答这个问题之前&#xff0c;我先分析下&#xff0c;在企业中做项目整个测试流程是什么样的&#xff0c;你清楚了整个企业的测试流程&#xff0c;就会清楚企业做测试需要什么&#xff1f;从而也就会明白如何去学测试。 1、需求&#xff1a; …

JDBC --- Java的数据库编程

目录 &#x1f348;一、数据库编程的必备条件 &#x1f349;二、什么是 JDBC JDBC 的优势 &#x1f34a;三、JDBC 使用流程 以及 常用接口和类的讲解 &#x1f361;0. 前置工作 &#x1f36d;1. 引入依赖 &#x1f36c;2. 数据库连接Connection &#x1f36c;3. 创建操…

11-基于51单片机电子密码锁门禁(实物图+原理图+源程序+仿真+论文)全套资料

编号: 011 本系统采用 51单片机 24C02芯片矩阵键盘 继电器 开锁指示灯 LCD1602液晶 蜂鸣器 而成 1.单片机型号:STC89C52/51、AT89C52/51、AT89S52/51可以任选。程序通用 2.采用矩阵按键输入、1602液晶显示、继电器模拟开锁、发光二极管为开锁指示灯&#xff0c;继电器是可以外…

适配器模式(九)

不管怎么样&#xff0c;都要继续充满着希望 上一章简单介绍了建造者模式(八), 如果没有看过, 请观看上一章 一. 适配器模式 引用 菜鸟教程里面的 适配器模式介绍: https://www.runoob.com/design-pattern/adapter-pattern.html 适配器模式&#xff08;Adapter Pattern&#…

出海品牌直播带货:虚拟主播的优势与挑战,以及未来趋势揭秘

随着全球化的发展和网络技术的进步&#xff0c;海外直播带货成为了品牌拓展海外市场的一种新方式。而在这个数字化时代&#xff0c;虚拟主播的出现给海外直播带货带来了全新的可能性。 在传统直播带货中&#xff0c;品牌需要派遣代表或明星代言人亲自现场演示产品&#xff0c;…

15、Nginx---slice模块,大文件分片请求

Nginx的slice模块可以将一个请求分解成多个子请求&#xff0c;每个子请求返回响应内容的一个片段&#xff0c;让大文件的缓存更有效率。。 HTTP Range请求&#xff1a; HTTP客户端下载文件时&#xff0c;如果发生了网络中断&#xff0c;必须重新向服务器发起HTTP请求&#xff0…