指针基础知识
一、 资源链接
-
59. 形参和实参_值传递和地址传递_哔哩哔哩_bilibili(系列课程,包括同系列的其他视频)
-
9.1.1 取地址运算:&运算符取得变量的地址_高清 720P_哔哩哔哩_bilibili(系列课程,包括同系列的其他视频)
二、实参与形参
在 C/C++ 中,参数传递涉及到实参和形参两个概念:
-
实参(实际参数):
- 实参是在函数调用时传递的具体值。
- 它可以是常量、变量或表达式,但必须具有确定的值。
- 在函数调用中,如果传递的是一个数组名,则传递的是该数组的首地址。
-
形参(形式参数):
- 形参数在函数的定义中指定,用于接收传递给函数的值。
- 形参必须指定类型,并只能是简单变量或数组,不能是常量或表达式。
- 形参类型与实参必须一致,个数和顺序也需要保持相同。如果类型不一致,C/C++ 会自动将实参转换为形参的数据类型。
2.2 值传递与地址传递
-
值传递:
- 当函数被调用时,实参的值被传递给形参。
- 这种传递是单向的,实参和形参占据不同的存储单元。
- 形参的改变不会影响到实参的值,即形参变化不反映在主调函数中。
-
地址传递:
- 如果函数接收的是指针或者数组名(等价于指向第一个元素的地址),形参实际上接收的是地址,这使得实参可以被修改。
2.3 内存分配与函数调用
-
内存分配:
- 在函数未被调用时,形参不占用内存。
- 函数调用时,系统会分配内存给形参,并将实参的对应值复制到形参中。
-
调用结束后:
- 函数执行完之后,其形参和返回值的内存会被释放。
小结
- 数据传递的本质是通过“值传递”的方式进行的,意味着在函数调用时,由系统在内存中新开辟区域用于计算操作。
- 当函数调用结束,其形参和返回值的内存将自动释放,确保内存资源的回收利用。
三、指针的概念定义及引用
3.1 指针的概念与定义
指针简介
在 C 语言中,指针是一个关键概念。它允许程序员直接操作内存地址,从而实现强大的功能和灵活性。理解指针的工作原理对于编写高效的 C 代码至关重要。
什么是指针?
-
内存地址:
- 在计算机中,每个变量都被存储在内存中一个唯一的地址上。这个地址可以用来访问和操作存储在那里的变量。
-
指针的定义:
- 指针是一个变量,用于存储另一个变量的内存地址。换句话说,指针的值指向的是另一个变量在内存中的位置。
指针的使用与语法
- 基本声明:
- 通过指针声明,可以创建一个用于保存内存地址的变量。例如:
int i = 3; // 声明一个整数变量 i,值为 3
int *i_pointer = &i; // 声明一个指针变量 i_pointer,存储变量 i 的地址
- 解引用:
- 使用解引用操作符(
*
)可以访问指针指向的变量的值。
- 使用解引用操作符(
int value = *i_pointer; // 获取指针 i_pointer 指向的变量 i 的值,结果为3
运算符的使用
- 地址取运算符(
&
):- 用于获取一个变量的内存地址。
int *p = &i; // 获取变量 i 的地址,并存储在指针 p 中
- 解引用运算符(
*
):- 用于访问指针所指向地址的内容。
int value = *p; // 访问指针 p 指向的整数的值
定义和初始化指针变量
- 类型匹配:
- 定义指针变量时,必须确保指针的类型与其指向的变量类型匹配。例如:
int *p1, *p2; // 定义两个整数指针
int a = 10, b = 20;
p1 = &a; // 将变量 a 的地址赋给指针 p1
p2 = &b; // 将变量 b 的地址赋给指针 p2
- 错误示例:
- 不能直接将整数赋值给指针,也不能将地址赋给一个非指针类型的变量。
3.2 指针的值交换和地址交换
在C语言中,可以使用指针来交换两个变量的值(值交换)或者交换两个指针的指向(地址交换)。
- 值交换:通过指针解引用改变指针指向的值。
void swapValues(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
- 地址交换:通过交换指针本身的值,使它们指向不同的变量。
void swapPointers(int **p1, int **p2) {
int *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
3.3 C++中的引用与指针之间的关系
引用是指针的一个高级抽象,简化了某些内存操作。
定义引用的注意事项
- 初始化要求:引用在定义时必须初始化。以下示例是错误的:
int &b; // 错误,引用必须初始化
- 不可重新绑定:引用一旦被初始化,无法重新绑定至其他对象。
- 数组引用限制:数组不能直接定义引用,因为数组表示一组数据,而引用只能作为单一对象的别名。
引用 vs 指针
引用和指针有几个关键区别:
- 不存在空引用:引用必须指向有效内存,而指针可以为 NULL。
- 不可重新绑定:引用在初始化后不允许指向别的对象,指针则可以随意调整指向。
- 初始化要求:引用在声明时需立即初始化,指针则可延迟。
引用的应用
- 函数参数传递:引用作为函数参数可以避免值传递时的复制开销,并简化接口设计。相比指针,引用语法上的简便使得代码更易读。
3.4 数组与指针之间的关系
- 数组名作为指针:数组名可以当作指向其首元素的指针。在函数定义中
int sum(int[])
等价于int sum(int*)
。 - 数组变量的地址:数组名本身表示地址,不需要
&
取地址。
int a[10];
int *p = a; // 等同于 int *p = &a[0];
- 数组单元地址获取:需要
&
以获得具体数组元素的地址。
a == &a[0]; // true
- 数组和指针运算:
[]
运算符即可以作用于数组,也可用于指针。
p[0] <==> a[0];
3.5 指针与常量
-
指向常量的指针:
const int *p1
或int const *p2
指针指向的内容不可变。 -
常量指针:
int *const p3
表示指针本身不可变,但其指向的内容可变。
常量转换
- 可以把非
const
变量的地址传给接受const
指针的函数参数,以保护函数内不修改该值。然而,const
变量的值不能被修改。
3.6 指针算术与运算
- 指针+1:使指针指向下一个相邻的内存单元。
int a[10];
int *p = a;
*(p+1) ==> a[1];
- 应确保指针指向连续空间如数组,才能有效进行算术运算。
指针自增和其他运算
*p++
:获取指针当前值后,再令指针指向下一个元素。这种操作在某些硬件上能简化为一条指令,提高效率。
指针比较
- 指针可以进行大小比较(
<
,<=
,==
,>
,>=
,!=
),需注意比较的是内存地址。
3.7 特殊的 0 地址
- 内存中有可能存在地址 0,但通常不可直接使用。
- 在指针中,0 地址或
NULL
表示特殊情况,比如未初始化的指针。 - 使用
NULL
或nullptr
(C++11 引入)来表示一个空指针更为稳妥。
3.8 指针的类型
- 统一大小:在多数系统中,所有指针的大小是相同的,因为它们存储的是内存地址。
- 类型安全:指向不同类型的指针不能相互赋值。这是因为指针类型定义了内存的解析方式。
- 指针类型正确性:应避免错误使用指针类型,以防止不期望的行为。
3.9 动态内存分配与指针的类型转换
-
void*** 指针**:
void*
表示指向未知类型的通用指针,可以用来进行各种类型指针之间的转换。- 需要显式类型转换才能用于计算或者存储特定类型数据值。
-
指针类型转换:
- 可以通过类型转换运算符将不同类型的指针相互转换。
int *p = &i;
void *q = (void*)p;
- 这种转换不会改变指针实际指向的数据类型,只是改变了对指针的解释方式。
3.10 使用指针的场景
-
数据传递:用于传递较大的数据以避免变量拷贝。
-
数组操作:传递数组指针以便函数对数组进行操作。
-
函数多结果返回:通过传入指针参数的方式从函数返回多个结果。
-
动态内存管理:申请和管理动态
四、a++
、++a
和 a = a + 1
的区别(题外)
以下是对 a++
、++a
和 a = a + 1
的区别和用法进行的整理:
操作 | 类型 | 操作描述 | 返回值 |
---|---|---|---|
a = a + 1 | 赋值语句 | 直接将 a 的值增加 1 | 无返回值 |
a++ | 表达式 | 返回 a 自增前的值,在表达式求值后将 a 增加 1 | a 增加前的原始值 |
++a | 表达式 | 在表达式求值前将 a 增加 1,然后返回增加后的值 | a 增加后的新值 |
三者的共同点
a++
、++a
、a = a + 1
都会使变量 a
的值增加1。
各自区别
a = a + 1
-
类型:赋值语句。
-
操作:直接将
a
的值增加1。 -
返回值:无返回值,因为赋值语句不返回值。
a++
(后缀自增)
-
类型:表达式。
-
操作:返回
a
自增前的值,在表达式求值后将a
增加1。 -
返回值:
a
增加前的原始值。
++a
(前缀自增)
-
类型:表达式。
-
操作:在表达式求值前将
a
增加1,然后返回增加后的值。 -
返回值:
a
增加后的新值。
表达式中的用法和语义
-
当这几个操作独立使用时,它们的共同点是都能使
a
增加1。 -
在复杂表达式中,
a++
和++a
的区别在于:a++
将参与计算的值是原始值。++a
将参与计算的值是增加后的新值。
结论
-
a++
和++a
都涉及自增操作,区别在于自增时机以及参与表达式计算的值。 -
a = a + 1
是明确的赋值操作,并不会参与其他表达式中作为计算的一个部分。