12-14_C基础(指针)
ptr + n = (int)ptr + sizeof(type)*n //运算结果为同类指针的常量
数组名是数组第0个元素的地址常量
type ary[M] =
ptr = ary;
ptr[n] = (type 8const)((int)ptr + sizeof(type)*n)
ary[n] = (type 8const)((int)ary + sizeof(type)*n)
举例:
type *A = …;
type *B = …;
pA - pB = (int)pA - (int)pB / sizeof(type)
后续cpp有引用,类似于指针,但是更好用一点
void *pv
void *
在 C 和 C++ 语言中是一个非常重要的类型,它表示一个指向未知类型数据的指针。以下是一些 void *
使用的场景:
通用指针:由于
void *
可以指向任何类型的数据,它可以被用作一个通用指针,用于在不同数据类型指针之间进行转换。内存操作:在执行内存分配(如使用
malloc
或calloc
)和释放(如使用free
)时,返回的类型是void *
,这样就可以用来分配任何类型的内存。函数参数:当函数需要处理不同类型的数据,但是又不希望为每种类型都定义一个函数时,可以使用
void *
作为函数参数,使得函数更加通用。数据结构:在实现某些数据结构如通用链表或树时,节点可以包含
void *
指针,以便它们能够存储任何类型的数据。 尽管void *
非常有用,但是使用它时需要注意以下几点:
类型安全:
void *
不支持指针算术操作,因为它不指向一个具体的类型,所以在使用前通常需要将其转换回原始类型。转换:将
void *
转换为其他类型的指针时,需要确保转换是正确的,以避免潜在的错误和程序崩溃。性能考虑:在某些情况下,使用
void *
可能会导致性能损失,因为它可能会阻止编译器进行某些优化。 例如,以下是一个使用void *
作为参数的函数,它简单地交换两个变量的值:
#include <stdio.h>
void swap(void *a, void *b, size_t size) {
char *temp = malloc(size);
if (temp == NULL) {
perror("Failed to allocate memory");
return;
}
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
free(temp);
}
int main() {
int x = 100, y = 200;
printf("Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y, sizeof(int));
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
在这个例子中,swap
函数能够交换任何类型的数据,只要传递正确的字节大小。这是 void *
通用性的一个体现。
#include <stdio.h>
#include <string.h>
char *foo(char szBuf[]) {
printf("%d\r\n", strlen(szBuf));
printf("%s\r\n", sizeof(szBuf));
char szBuf2[20];
strcpy(szBuf2, szBuf);
printf("%d\r\n", strlen(szBuf2));
printf("%s\r\n", sizeof(szBuf2));
return szBuf2;
}
//猜猜每个printf输出什么。
int main() {
char szBuf1[] = "Hello World";
char *szBuf2 = "Hello World";
printf("%d\n", strlen(szBuf1));
printf("%d\n", strlen(szBuf2));
printf("%d\n", sizeof(szBuf1));
printf("%d\n", sizeof(szBuf2));
printf("%d\n", sizeof("Hello world"));
char *psz = foo(szBuf1);
puts(psz); //猜猜这个puts会输出什么?
printf("%d\n", strlen(psz)); //guess
printf("%d\n", sizeof(psz)); //guess
}
函数指针
函数指针是C/C++中的一种特殊指针,它指向函数,而不是变量。通过函数指针,你可以在程序运行时动态地调用不同的函数,这为代码带来了更多的灵活性。
1. 什么是函数指针?
函数指针就是保存某个函数地址的指针。因为函数在内存中也占有一块地址空间,所以我们可以将这个地址存储在一个指针变量中,并通过指针调用该函数。
2. 函数指针的定义
函数指针的声明格式有些特殊,形式如下:
return_type (*pointer_name)(parameter_list);
其中:
return_type
是函数的返回类型。parameter_list
是函数的参数列表(类型和顺序要与指向的函数一致)。pointer_name
是函数指针的名称。
例如,假设有一个返回 int
类型,并且接受两个 int
参数的函数,则它的函数指针可以这样定义:
int (*func_ptr)(int, int);
这个定义表示 func_ptr
是一个指向返回类型为 int
且参数类型为两个 int
的函数的指针。
3. 函数指针的使用
下面通过一个简单的例子说明如何定义和使用函数指针。
定义普通函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
定义函数指针并使用
#include <stdio.h>
// 定义两个函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
// 定义一个函数指针,指向具有两个int参数并返回int类型的函数
int (*func_ptr)(int, int);
// 让函数指针指向add函数
func_ptr = add;
printf("Result of add: %d\n", func_ptr(10, 5)); // 输出 15
// 让函数指针指向subtract函数
func_ptr = subtract;
printf("Result of subtract: %d\n", func_ptr(10, 5)); // 输出 5
return 0;
}
4. 函数指针作为参数
你可以将函数指针作为参数传递给另一个函数,这样可以让函数根据不同的逻辑调用不同的函数。例如:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
// 操作函数,接收一个函数指针作为参数
int operation(int (*func)(int, int), int x, int y) {
return func(x, y);
}
int main() {
int result;
result = operation(add, 10, 5);
printf("Result of add: %d\n", result); // 输出 15
result = operation(subtract, 10, 5);
printf("Result of subtract: %d\n", result); // 输出 5
return 0;
}
5. 函数指针数组
如果你有一组函数具有相同的参数和返回类型,你可以创建一个函数指针数组,将这些函数存储在数组中。这样你可以根据需要调用不同的函数。
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
// 定义函数指针数组
int (*func_ptrs[3])(int, int) = {add, subtract, multiply};
// 调用数组中的不同函数
printf("Add: %d\n", func_ptrs[0](10, 5)); // 输出 15
printf("Subtract: %d\n", func_ptrs[1](10, 5)); // 输出 5
printf("Multiply: %d\n", func_ptrs[2](10, 5)); // 输出 50
return 0;
}
6. 回调函数与函数指针
函数指针常用在回调函数中。当你希望某个函数在某些事件发生时调用另一个函数,可以通过传递函数指针来实现。这种机制在事件驱动的编程和信号处理等场景下非常有用。
7. 注意事项
函数指针指向的函数必须与指针的声明类型匹配,包括参数类型和返回值类型。
函数指针的用途非常广泛,常用于回调机制、动态调用函数、多态实现等。
总结来说,函数指针赋予了程序更高的灵活性,尤其是在需要动态选择或改变函数的场景下,函数指针可以极大地简化代码结构。
数组与指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ()
{
int ary[3][4] = {
{1, 2, 3, 4},
{10, 20, 30, 40},
{100, 200, 300, 400}
};
//数组名是第0个元素的指针常量
//二维数组的元素是一维数组
//int ary[3][4]的元素是int[4]
//ary是int[4]类型的指针常量
printf("ary = %p\r\n", ary);
//*ary得到int[4]的一维数组
//*ary得到int类型的指针常量
printf("*ary = %p\r\n", *ary);
//任何类型的变量取地址得到该类型的指针
//&ary取地址得到int[3][4]类型的指针
printf("&ary = %p\r\n", &ary);
printf("&ary[0] = %p\r\n", &ary[0]);
printf("ary[0] = %p\r\n", ary[0]);
printf("ary[0][0] = %p\r\n", ary[0][0]);
//ary是int[4]类型的指针常量
//ary + 1 ==> (int)ary + 1 * sizeof(int[4])
printf("ary + 1 =%p\r\n", ary + 1);
//*ary是int[4]类型的指针常量
//*ary + 1 ==> (int)*ary + 1 * sizeof(int)
printf("*ary + 1 = %p\r\n", *ary + 1);
//&ary取地址得到int[3][4]类型的指针常量
//&ary + 1 ==> (int)&ary + 1 * sizeof(int[3][4])
printf("&ary + 1 = %p\r\n", &ary + 1);
//二维数组的元素是一维数组
//ary[0]是int[4]数组
//数组名是第0个元素的指针常量
//int[4]是int类型的指针常量
printf("ary[0] + 1 = %p\r\n", ary[0] + 1);
//指针加整形得到同类型的指针常量
//ary是 int[4]类型的指针常量
//ary + 1得到int[4]类型的指针常量
//*(ary + 1)得到一维数组int[4]
//数组名是第0个元素的指针常量
//*(ary + 1)是int类型的指针常量
//对int类型的指针常量做[1]运算得到int类型的指针常量
printf("(*(ary + 1))[1] = %p\r\n", (*(ary + 1))[1]);
}
int (*p)[4] = ary;
int (*pAry)[3][4] = &ary;
printf("%p\r\n", p);
printf("%p\r\n", *p);
printf("%p\r\n", pAry);
printf("%p\r\n", *pAry);
printf("%p\r\n", **pAry);
printf("%p\r\n", p + 1);
printf("%p\r\n", *p + 1);
printf("%p\r\n", pAry + 1);
printf("%p\r\n", *pAry + 1);
printf("%p\r\n", **pAry + 1);
int n = 6;
int *pN = &n;
int **pPN = &pN;
printf("%p\r\n", pPN);
printf("%p\r\n", *pPN);
printf("%p\r\n", **pPN);
解引用
解引用(Dereference)是计算机编程中处理指针(或引用)时的一个重要概念,通常指从指针中获取其指向的目标值。解引用通过访问指针存储的地址并读取该地址处的实际数据,从而间接操作数据。解引用操作允许程序通过指针修改和访问变量的内容。
在不同的编程语言中,解引用的操作和语法可能有所不同。下面分别介绍一些常见语言中的解引用操作:
1. C/C++ 中的解引用
在 C 和 C++ 中,指针是存储变量地址的变量。解引用指针可以访问或修改指针指向的内容。使用 *
操作符来解引用一个指针。
#include <stdio.h>
int main() {
int x = 10;
int *p = &x; // p 是指向 x 的指针
printf("x = %d\n", *p); // 解引用 p,访问 x 的值
*p = 20; // 通过解引用修改 x 的值
printf("x = %d\n", x); // x 现在为 20
return 0;
}
&x
获取变量x
的地址,存储在指针p
中。*p
解引用指针p
,从而访问或修改x
的值。
2. C++ 中的引用和解引用
C++ 引入了引用的概念,引用是变量的别名,解引用操作变得更加直观,不需要使用 *
操作符。
#include <iostream>
int main() {
int x = 10;
int &ref = x; // ref 是 x 的引用,直接操作 x
std::cout << "x = " << ref << std::endl; // 访问 x 的值
ref = 20; // 修改 x 的值
std::cout << "x = " << x << std::endl; // x 现在为 20
return 0;
}
int &ref = x;
表示ref
是x
的引用,ref
可以直接当作x
来使用。
3. Rust 中的解引用
Rust 也使用 *
操作符进行解引用。Rust 提供了非常安全的内存管理,确保指针不会产生空指针解引用等问题。
fn main() {
let x = 10;
let p = &x; // p 是 x 的引用(指针)
println!("x = {}", *p); // 解引用 p 访问 x 的值
}
Rust 强调所有权与借用的概念,&x
是一个不可变引用(类似于指针),而 *p
解引用引用 p
来访问 x
的值。
4. Python 中的解引用
在 Python 中,变量是引用类型,虽然没有显式的指针,但可以理解所有变量实际上都持有对象的引用。虽然 Python 没有直接的指针操作,但通过类似方式可以实现对象的间接访问。
x = [10]
p = x # p 是对列表 x 的引用
print(p[0]) # 访问 x 中的值
p[0] = 20 # 修改 x 中的值
print(x[0]) # x 现在为 20
这里,p
和 x
都指向同一个列表,通过 p
可以修改 x
的内容,类似于解引用。
5. Java 中的解引用
Java 中没有显式的指针操作,但所有非基本类型变量都可以视为对象引用。对引用变量的访问和操作也可以理解为一种隐式的解引用。
class Main {
public static void main(String[] args) {
int[] x = {10};
int[] p = x; // p 是对 x 的引用
System.out.println(p[0]); // 访问 x 中的值
p[0] = 20; // 修改 x 中的值
System.out.println(x[0]); // x 现在为 20
}
}
Java 中的引用变量 p
实际上指向了对象的内存地址,因此 p[0]
和 x[0]
都是对相同内存地址的数据的访问。
6. 解引用中的常见错误
空指针解引用(Null Pointer Dereference):试图解引用一个
null
或未初始化的指针会导致程序崩溃或未定义行为。悬空指针(Dangling Pointer):指针指向一个已经释放的内存地址,解引用悬空指针可能会导致不可预知的错误。
例如,C/C++ 中最常见的空指针解引用:
int *p = NULL;
printf("%d", *p); // 错误:空指针解引用
解引用是通过指针或引用间接访问数据的过程。在现代编程语言中,解引用操作常见于指针操作、引用变量等场景。不同语言的解引用语法可能不同,但其核心概念是一致的,即通过存储的地址访问实际的数据。
二维数组和二级指针补充
二维数组与二级指针在 C 语言中是常见的概念,它们都涉及到指针,但本质上是两种不同的结构。下面我们详细讲解它们的区别、联系和如何使用。
一、二维数组
1. 二维数组的定义
二维数组是一种存储二维数据的数组,在 C 语言中常用于表示表格或矩阵。一个二维数组可以看作是一个数组的数组,具体来说,每一行都是一个一维数组。
例如,定义一个 2x3 的二维数组:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
这个数组的存储布局是连续的。它存储的是 2 行 3 列的 int
值。在内存中,array
的元素按照行优先顺序存储,即:
| 1 | 2 | 3 | 4 | 5 | 6 |
2. 访问二维数组
访问二维数组的方式是通过行和列的索引来访问具体的元素。例如:
array[0][1]
访问第一行第二列的元素,结果为2
。array[1][2]
访问第二行第三列的元素,结果为6
。
3. 二维数组的指针表示
在 C 语言中,数组的名字可以视作指向数组第一个元素的指针。对于二维数组来说,array
可以看作是指向第一行的指针:
array
是一个指向array[0]
的指针,array[0]
是指向第一个一维数组的指针,即array[0]
本质上是int *
。
通过指针访问二维数组:
*array
是array[0]
,即指向第一行的指针(int *
)。*(array + 1)
是array[1]
,即指向第二行的指针(int *
)。
例如:
printf("%d\n", *(*(array + 1) + 2)); // 访问 array[1][2],结果为 6
二、二级指针(int **
)
1. 二级指针的定义
二级指针是指指向指针的指针。简单来说,二级指针是一个指向一级指针(例如 int *
)的指针。可以通过二级指针访问一个指针所指向的内存地址。
例如,定义一个二级指针:
int a = 10;
int *p = &a; // p 是一级指针,指向 a
int **pp = &p; // pp 是二级指针,指向 p
这里:
p
是一级指针,指向int
类型的变量a
,它存储了a
的地址。pp
是二级指针,指向一级指针p
,它存储了指针p
的地址。
通过 pp
可以间接访问 a
的值:
printf("%d\n", **pp); // 输出 a 的值,结果为 10
2. 二级指针的用途
二级指针最常见的用途是在函数中传递指针并修改原始指针的值。因为 C 语言中函数参数是按值传递的,如果需要在函数中改变一个指针的值(例如动态分配内存),就需要传递二级指针。
例如,使用二级指针动态分配二维数组:
#include <stdio.h>
#include <stdlib.h>
void allocateMemory(int ***array, int rows, int cols) {
*array = (int **)malloc(rows * sizeof(int *)); // 分配指向每一行的指针数组
for (int i = 0; i < rows; i++) {
(*array)[i] = (int *)malloc(cols * sizeof(int)); // 为每一行分配内存
}
}
int main() {
int **array;
allocateMemory(&array, 2, 3); // 传递二级指针进行内存分配
// 初始化二维数组
array[0][0] = 1; array[0][1] = 2; array[0][2] = 3;
array[1][0] = 4; array[1][1] = 5; array[1][2] = 6;
// 打印数组
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// 释放内存
for (int i = 0; i < 2; i++) {
free(array[i]);
}
free(array);
return 0;
}
三、二维数组与二级指针的联系和区别
1. 联系:
访问方式:二维数组和二级指针在访问时的方式类似。你可以使用双重解引用
**
来访问二维数组中的元素,或者通过指针偏移来访问数组元素。存储结构:无论是二维数组还是通过二级指针动态分配的数组,它们的内存布局可以是连续的(在静态数组的情况下)或非连续的(在动态分配的情况下)。
2. 区别:
内存分配:二维数组的内存是静态分配的,编译器为其分配连续的内存空间。而二级指针可以通过
malloc
动态分配内存,每一行的内存地址可以是不连续的。声明方式:二维数组的声明如
int array[2][3]
,而二级指针需要声明为int **
,并且可以动态分配内存。访问元素:二维数组的元素可以直接通过
array[i][j]
访问,而二级指针的元素访问需要通过双重解引用*(*(array + i) + j)
或等价的array[i][j]
。
四、例子:静态二维数组和动态二级指针分配的比较
静态二维数组:
int array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
动态分配的二维数组(使用二级指针):
int **array = (int **)malloc(2 * sizeof(int *)); // 动态分配2行
for (int i = 0; i < 2; i++) {
array[i] = (int *)malloc(3 * sizeof(int)); // 每行动态分配3列
}
// 给数组赋值
array[0][0] = 1; array[0][1] = 2; array[0][2] = 3;
array[1][0] = 4; array[1][1] = 5; array[1][2] = 6;
五、总结
二维数组是存储二维数据的数组,内存分配是静态的,存储布局是连续的。
二级指针是指向指针的指针,可以用于动态分配二维数组的内存,内存布局可以是连续或不连续的。
它们都可以用于表示矩阵或二维表格,但是在内存分配和访问方式上有所区别。