(一三一)指向对象的指针

简介:

类对象也可以像基本类型那样,使用指针。

 

假如有Man类的对象a,可以使用指针指向它:

Man* c=&a;

这里表示指针c(类型为Man)指向对象a的地址(使用地址运算符&)。

 

也可以使用new

Man *a=new Man;

这是new一个动态内存地址,类型为Man,然后用a指向地址。

new的过程中,调用默认构造函数创建一个对象,被放在堆,而指针a指向的则是这个对象的地址。

 

 

 

关于析构函数的调用:

①当离开对象的作用域时,析构函数将被调用(例如代码块内声明的对象,在离开代码块时);

 

②静态对象,将在程序执行完毕时,调用静态对象的析构函数(main()函数结束);

 

new出来的对象,只在使用delete时,调用对象的析构函数。

 

 

当指针遇见对象时的几种情况:

假设类为Man,已声明对象Man one(注,已被初始化赋值);

Man *a;

一个普通的Man类型指针,尚未指向任何对象

 

Man *a=&one;

指针指向对象one的地址。

 

Man *a = new Man;

指针使用new申请动态内存创建新的Man对象,调用Man的 默认构造函数 

 

Man *a = new Man(one);

指针使用new申请动态内存创建新对象,并同时以对象one为参数进行初始化,调用Man的 复制构造函数 

 

Man *a = new Man[5];

指针指向动态数组,数组类型为Man,元素个数为5,并且每个都调用 默认构造函数 进行初始化。

 

Man *a = new Man("aabbcc");

指针new一个对象,这个对象用参数"aabbcc"进行初始化(假如有多个参数的话,可以用例如 (1,"aabbcc") 这样。调用 构造函数 。

 

 

 

当类指针调用类函数方法时:

已知,结构指针可以通过 -> 运算符来访问结构成员,也可以通过 (*结构对象).结构成员 来访问。类的指针也类似。

例如:

Man*a = new Man(1,"abc");

(*a).show(); //办法一

a->show(); //办法二

 

这两个的效果,是一样的。

 

 

 

 

new定位运算符:

已知,new定位运算符的效果是,使用某个指定地址的内存。

前提一:需要使用头文件<new>

前提二:这个地址不一定在堆中(甚至不一定需要同一个类型,比如char*使用int类型变量的地址)

前提三:程序员需要为这种使用负责(不会像new那样在堆中自动申请够足够的动态内存)

 

 

 

作用:

根据我搜索的一些内容显示,new定位运算符的作用有(有些意思可能是相互重复的):

①在特定地址调用构造函数

构造函数和析构函数的指针是无法获得的,那么怎么单纯的调用构造函数呢,这时候定位new就有用了

 

②硬件编程

如果知道了硬件设备的地址,想要将那个硬件设备与一个C++类直接关联,那么定位new就非常有效了

 

③实现基础库

基础库一般为了效率要先预分配内存,然后在预分配的内存上执行构造,几乎所有的C++容器10种,但我尚没搞懂是什么)都用到了定位new

 

④在先分配好的缓冲区中划分出一部分用作我们自己的内存规划。例如先new char[40],然后在这40char宽度的内存中,再自行使用。

 

⑤在特定位置创建对象。

 

注意:在同一个地址使用new,很可能会覆盖之前的数据。

 

大概就是以上作用。

 

配对:

new可以和delete,也需要和delete配对使用;

但是new定位运算符,不能和delete配对使用。

 

 

缓存区:

大概意思就是预先准备的一部分内存,

例如new char[80]就是80char宽度的内存,在未被delete前都不会被占用。

也可能是char a[80],这部分就在栈区了。

有点划地盘的感觉。

 

 

析构函数:

使用new定位运算符创建的对象,不应使用delete/delete[]

原因在于,new定位运算符一般被放在缓冲区之中(缓冲区有可能的是使用new xx[]申请的,但也有可能是在栈、或者静态存储区),这可能导致错误。

 

对于使用new定位运算符创建的对象,应该 显式的使用析构函数 。

这也是需要显式使用析构函数的少数几种情形之一。

 

因为是指针,所以要注意运算符。例如:one->~Man();

 

关于使用顺序,晚创建的对象(使用new定位运算符,特别是在同一地址),应该先调用析构函数(因为晚创建的对象,可能依赖于早创建的,不过不懂这句话),早创建的后调用析构函数。(有点类似栈的LIFO

 

之所以必须显式的调用析构函数,因为假如不显示调用析构函数的话,那么类对象则不会被销毁(就像使用new的对象一样,不受代码块结束所限制,只有使用了delete才会被调用析构函数而销毁)。

 

 

 

防止覆盖:

假如已经在缓存区内某个内存地址(one)使用new定位运算符创建了一个对象a,然后又想在这个缓存区内创建一个对象b,但不想让b覆盖对象a。那么就应该使用运算符sizeof()。

 

运算符sizeof的作用是返回对象的内存宽度,例如

cout << sizeof(*b) << endl;

cout << sizeof(Man<< endl;

这二者貌似差不多,不过书上用的是后者。

 

问题:这二者有区别么?

 

而在防止覆盖时,是这么使用的:

Man*b = new(one + sizeof(Man)) Man;

如果有需要,还可以在最后一个Man后面加上 ("qqq")之类的参数,调用 构造函数 进行初始化。

 

 

如代码:

#include<iostream>
#include<new>	//第一次我忘记加了,但感觉加不加似乎结果都一样

class Player
{
	char* name;	//名字(指针)
	int id;	//编号(整型)
	static int num;	//总数(计数器)
public:
	Player();	//默认构造函数
	~Player();	//析构函数
	void show();	//显示
};

Player::Player()
{
	name = new char[2];	//使用new[],那么对应应该使用delete[],且其他构造函数应该保持一致
	*name = 'a' + num;	//空字符占位
	name[1] = '\0';
	id = ++num;
	std::cout << "地址:" << this << ",姓名:" << name << ",ID:" << id << " 被创建,当前对象总数:" << num << std::endl;	//通报地址及其他数据
}
Player::~Player()	//注意波浪线位置
{
	std::cout << "地址:" << this << ",姓名:" << name << ",ID:" << id << " 已经删除,当前对象总数:" << --num << std::endl;	//通报地址及其他数据
	delete[]name;
}
void Player::show()
{
	std::cout << "地址:" << this << ",姓名:" << name << ",ID:" << id << ",当前对象总数:" << num << std::endl;
}
int Player::num = 0;
int main()
{
	using namespace std;
	Player *B, *C, *D;
	char *M;	//代码块外定义指针

	{
		cout << "*************这里代码块开始*************" << endl;
		Player a;
		Player *b = new Player;
		char *m = new char[100];	//创建缓存区,宽度为100个char
		cout << "缓存区m的地址是:" << (void*)m << ",以下2个使用new定位运算符" << endl;	//这里之所以用(void*m),是因为m指向字符串,默认显示字符串,所以需要使用强制类型转换来显示地址
		Player*c = new(m)Player;	//使用new定位运算符
		Player *d = new(m + sizeof(Player))Player;	//使用new定位运算符在缓存区,保证新对象不覆盖老对象
		B = b, C = c, D = d, M = m;	//让代码块外定义的指针分别指向代码块内定义的指针,防止代码块结束后,声明的指针消失
	}
	cout << "*************这行之前,代码块结束*************" << endl;
	delete B;	//之所以不能deleteb,是因为b是在代码块内定义的

	D->~Player();
	(*C).~Player();	//显式调用对象指针的两种方式,先用的后调用
	delete[]M;	//最后再删除缓存区
	system("pause");
	return 0;
}

显示:

*************这里代码块开始*************
地址:003EFE8C,姓名:a,ID:1 被创建,当前对象总数:1
地址:00470DC0,姓名:b,ID:2 被创建,当前对象总数:2
缓存区m的地址是:00470AE8,以下2个使用new定位运算符
地址:00470AE8,姓名:c,ID:3 被创建,当前对象总数:3
地址:00470AF0,姓名:d,ID:4 被创建,当前对象总数:4
地址:003EFE8C,姓名:a,ID:1 已经删除,当前对象总数:3
*************这行之前,代码块结束*************
地址:00470DC0,姓名:b,ID:2 已经删除,当前对象总数:2
地址:00470AF0,姓名:d,ID:4 已经删除,当前对象总数:1
地址:00470AE8,姓名:c,ID:3 已经删除,当前对象总数:0
请按任意键继续. . .


目录
相关文章
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
36 4
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
30 2
|
1月前
对象指针输出时分秒
对象指针输出时分秒
9 0
|
3月前
|
编译器 C++
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
|
5月前
|
编译器 C++
函数指针和函数对象不是同一类型怎么替换
函数指针和函数对象不是同一类型,为何可替换用作同一函数的参数
|
5月前
|
算法 Java 程序员
面向对象编程(OOP)通过对象组合构建软件,C语言虽是过程式语言,但可通过结构体、函数指针模拟OOP特性
【6月更文挑战第15天】面向对象编程(OOP)通过对象组合构建软件,C语言虽是过程式语言,但可通过结构体、函数指针模拟OOP特性。封装可使用结构体封装数据和方法,如模拟矩形对象。继承则通过结构体嵌套实现静态继承。多态可通过函数指针模拟,但C不支持虚函数表,实现复杂。C语言能体现OOP思想,但不如C++、Java等语言原生支持。
64 7
|
5月前
类与对象\this指针
类与对象\this指针
33 0
|
5月前
|
C++
【C++系列】指针对象和对象指针的区别
这段内容介绍了C++中`ListNode`对象和指针的两种使用方式以及它们的区别。首先,`ListNode dummy(0); ListNode* cur = &dummy;创建了一个`ListNode`对象`dummy`在栈上,`cur`是`dummy`的地址。而`ListNode* dummy = new ListNode(0); ListNode* cur = dummy;`则在堆上分配了一个`ListNode`,`dummy`和`cur`都是指向该对象的指针。使用`&dummy`作为虚拟头节点简化链表操作,避免特殊处理。栈分配内存自动管理但生命周期受限,堆分配内存需手动释放且速度较慢。
|
5月前
|
存储 安全 编译器
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
45 0
|
6月前
|
存储 编译器 程序员
从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针(下)
从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针
25 0