C++ 中的指针、指针对象、普通对象、引用类型等概念理解
1. 指针
C++ 中的指针是一个非常重要的概念,它允许你直接访问内存中的地址,可以用来操作数据、实现数据结构,以及进行动态内存分配。下面是一些关于 C++ 指针的基本理解和用法:
指针的定义
指针是一个变量,其值是一个内存地址。你可以通过在变量名前面加上星号 * 来定义指针。例如:
1 |
|
取地址操作符
你可以使用取地址操作符 & 来获取一个变量的地址,将其存储在指针中。例如:
1 |
|
访问指针指向的值
你可以使用解引用操作符 * 来访问指针指向的内存位置的值。例如:
1 |
|
指针的空值
指针可以有一个空值,表示它不指向任何有效的内存位置。可以使用 nullptr 或 NULL(在旧的 C++ 标准中)来将指针设置为空值。
1 |
|
指针的算术运算
指针可以进行算术运算,如递增、递减和加法。这通常用于遍历数组或访问连续内存块。
1 |
|
指针与数组
数组名本质上是一个指向数组第一个元素的指针。你可以使用指针来遍历数组元素。
1 |
|
指针与函数
指针还可以用于传递函数参数,允许函数修改调用者提供的数据。
1 |
|
动态内存分配
使用 new 运算符可以在堆上分配内存,并返回指向分配内存的指针。使用 delete 运算符释放这些内存。
1 |
|
2. 普通对象和指针对象
C++ 中的普通对象和指针对象有明显的区别,这些区别主要涉及到它们的声明、内存分配、访问方式和生命周期等方面。
声明方式:
- 普通对象:普通对象的声明方式不包含指针符号 *,直接声明对象的类型和名称。
- 指针对象:指针对象的声明需要在类型前面加上 * 符号,以表明它是一个指向某种类型的指针。
1
2int x; // 普通整数对象
int *ptr; // 整数指针对象
内存分配:
- 普通对象:普通对象通常在栈上分配内存。它们的生命周期受限于包含它们的作用域。
- 指针对象:指针对象可以指向栈上的对象或堆上分配的对象。通过 new 运算符可以在堆上分配内存来存储对象。
1
2int y; // 普通整数对象,栈上分配
int *ptr = new int; // 整数指针对象,指向堆上分配的整数
访问方式:
- 普通对象:可以直接使用对象的名称访问其值。
- 指针对象:需要使用解引用操作符 * 来访问指针对象指向的值。
1
2int value = y; // 直接访问 y 的值
int value2 = *ptr; // 使用指针解引用访问指向的整数的值
生命周期:
- 普通对象:普通对象的生命周期通常与其包含的作用域相同。当离开作用域时,它们将自动被销毁。
- 指针对象:指针对象的生命周期通常需要手动管理。如果它们指向堆上分配的内存,你需要使用 delete 来释放内存,以避免内存泄漏。
1
delete ptr; // 释放指针对象 ptr 所指向的堆上内存
示例
假设你有一个名为 Person 的 C++ 类,该类包含姓名和年龄属性:
1 |
|
创建普通对象:
1 |
|
在上述示例中,我们直接创建了一个名为 person1 的普通对象,然后访问了其属性和成员函数。
创建指针对象:
1 |
|
在上述示例中,我们使用 new 运算符创建了一个指向 Person 类的对象的指针 personPtr,然后使用箭头操作符 -> 访问对象的属性和成员函数。最后,我们使用 delete 运算符释放了动态分配的内存,以避免内存泄漏。
C++ 中的 .(点号)和 ->(箭头)
在C++中,.(点号)和 ->(箭头)是用于访问对象成员的两个不同操作符,它们的使用方式和含义有一些重要区别,取决于对象的类型。
点号 .:
- 用于直接访问普通对象的成员,包括类的数据成员和成员函数。
- 通常用于栈上或静态存储的对象,以及引用对象的成员。
1
2
3SomeClass obj; // 创建 SomeClass 类的对象
obj.memberVar = 42; // 访问对象的数据成员
obj.memberFunction(); // 调用对象的成员函数
箭头 ->:
- 用于访问指针对象所指向的对象的成员,包括数据成员和成员函数。
- 通常用于堆上或动态分配的对象,或者当你有一个指向对象的指针时。
1
2
3SomeClass *ptr = new SomeClass(); // 创建 SomeClass 类的对象指针
ptr->memberVar = 42; // 访问指针所指向对象的数据成员
ptr->memberFunction(); // 调用指针所指向对象的成员函数
总结:
- 点号 . 用于直接访问对象的成员,通常用于普通对象。
- 箭头 -> 用于通过指针访问对象的成员,通常用于指针对象或对象的引用。
3. 引用类型
声明方式:
- 引用类型使用 & 符号来声明,它是一个别名,引用一个已存在的变量或对象。
- 引用必须在声明时初始化,一旦引用了某个变量或对象,它将一直引用该变量或对象,不能改变引用的目标。
1
2int x = 42;
int &ref = x; // 引用 x
指向:
- 引用是原变量的别名,不会分配新的内存。
- 引用只能引用已存在的变量或对象。
4. C++ 和 C# 对比
- 在 C# 中的引用类型相当于 C++ 中的指针类型,尽管在语法和实现上有一些不同,但它们都用于引用对象而不是直接包含对象的数据。
- 在 C++ 中的普通对象,与 C# 中的引用类型(Reference Type)并没有一个直接的等价物。
- 然而,如果要寻找一个更接近的概念,C++ 中的普通对象可能与 C# 中的值类型(Value Type)有一些相似之处。在 C# 中,值类型包括结构 (struct) 和基本数据类型(如 int、double),它们在栈上分配内存,不受垃圾回收器管理,与 C++ 中的普通对象在内存分配和生命周期方面有些相似。
5. 栈内存和堆内存
分配方式:
- 栈内存:栈内存是一种静态内存分配,用于存储函数调用的局部变量、函数参数和函数调用的返回地址。它的分配和释放是自动的,随着函数的进入和退出而发生。
- 堆内存:堆内存是一种动态内存分配,用于存储程序中动态分配的数据,如通过 new、malloc 或类似的分配函数分配的对象。堆内存的分配和释放需要显式管理,否则可能导致内存泄漏。
速度:
- 栈内存:栈内存的分配和释放速度通常比较快,因为它是通过简单的移动栈指针来实现的。
- 堆内存:堆内存的分配和释放速度可能较慢,因为它需要更复杂的内存管理,包括查找合适的内存块,分配和释放。
大小限制:
- 栈内存:栈内存的大小通常有限,因为它的空间由系统自动管理,同时还受到递归函数调用的限制。通常,栈的大小在几兆字节到几百兆字节之间。
- 堆内存:堆内存的大小通常较大,受系统内存的总大小限制。在大多数现代系统中,堆可以非常大。
生命周期:
- 栈内存:栈内存中的数据的生命周期与其包含的作用域相对应,当作用域结束时,栈上的数据会自动销毁。
- 堆内存:堆内存中的数据的生命周期需要手动管理。如果不释放堆上分配的内存,可能会导致内存泄漏。
访问:
- 栈内存:栈上的数据访问速度较快,因为它们通常在内存中紧密排列,利于缓存。
- 堆内存:堆上的数据访问速度较慢,因为它们分散在内存中,需要通过指针来访问。
适用情况:
- 栈内存:适用于存储生命周期较短的局部变量和函数参数。它通常用于维护程序的执行状态和函数调用。
- 堆内存:适用于存储动态分配的数据,如复杂数据结构、对象、数组等,其生命周期可能跨越多个函数调用。