当前位置: 首页 > news >正文

指针----------C语言经典题目(2)

指针基础

什么是指针,介绍一下你对指针的理解

指针是一种特殊的变量,存储的是内存地址,而不是普通数据的值。

比如int *p;表示p是一个指针,只能存储int类型数据的内存地址。

指针的类型(如int *、char *)决定了他能指向的数据类型,以及通过指针访问内存时的操作长度,比如int *指针每次移动会跨越4字节,对应int 的大小。

内存地址:通过&符号可以获取变量的地址。

int a = 10;
int *p = &a;  // p 存储变量 a 的内存地址

可以通过解引用符号 *,来间接访问指针所指向的内存中的值:

int a = 5, *p = &a;
printf("%d", *p);  // 输出 5(通过指针 p 访问 a 的值)
*p = 10;           // 通过指针修改 a 的值,此时 a 变为 10

指针的算术运算:指针可以进行加减整数操作,结果与指针类型相关:

int a[3] = {1, 2, 3};
int *p = a;        // p 指向数组首元素(a[0])的地址
p++;                 // p 指向 a[1] 的地址(地址增加 sizeof(int), 4 字节)
printf("%d", *p);   // 输出 2

如何定义和初始化一个指针变量?

定义:

基本格式为:数据类型 *指针变量名

比如:

int *p;        // 定义一个指向 int 类型的指针变量 
char *p;    // 定义一个指向 char 类型的指针变量 
float *p;    // 定义一个指向 float 类型的指针变量 

初始化指针变量:

需要为其赋一个合法的内存地址,或初始化为NULL空指针。

1.指向普通变量的地址:
int n = 10;
int *p = &n;  // 定义时初始化,p 指向变量 n 的地址
2.指向数组元素的地址:
int a[5] = {1, 2, 3, 4, 5};
int *p = a;    // 初始化指向数组首元素(等价于 &a[0])
int *p2 = &a[2];  // 初始化指向数组第 3 个元素(值为 3)
3.指向动态分配的内存(使用malloc)
int *p = (int *)malloc(sizeof(int));  // 分配一个 int 类型的内存空间
*p = 100;  // 通过指针操作内存空间

什么是地址运算符(&)和解引用运算符(*)?

地址运算符&:

作用是获取一个变量的内存地址:

int a = 10;
int *p = NULL; // 定义一个指向 int 类型的指针变量 p
p = &a; // 将变量 a 的地址赋给指针 p(&a 表示获取 a 的地址)

每个变量在内存中都有一个唯一的地址,&用于获取这个地址。

指针变量(p)的作用是存储其他变量的地址,而&是将变量的地址赋值给指针。

解引用运算符*:

作用是通过指针变量访问它所指向的内存地址中的值:

int a = 10;
int *p = &a; // p 指向 a 的地址
printf("%d\n", *p); // 输出 10(*p 表示访问 p 指向的地址中的值,即变量 a 的值)
*p = 20; // 通过指针修改 a 的值,此时 a 的值变为 20

*用于操作指针所指向的数据,当指针指向一个变量时,*p等价于该变量本身。

*p = 20           等价于   a = 20。

空指针是什么?如何使用?

空指针是一个指针变量,其值为NULL。

1.初始化的时候使用:

int *p = NULL; // 声明时初始化为空指针

是合法的指针值,这样的初始化可以避免成为野指针。

2.函数返回值的错误标识

使用malloc时,内存分配失败时返回NULL,可以用if语句来判断内存是否分配失败。

int *a = (int*)malloc(sizeof(int));
if (arr == NULL) 
{printf("内存分配失败!\n");
}

3.数据结构的结束标志

在链表、树等数据结构中,空指针常用于表示节点的末尾(如链表的尾节点指针指向NULL)

struct Node {int data;struct Node *next;
};struct Node *head = NULL; // 空链表的头指针为 NULL

野指针是什么?如何避免?

野指针是指向非法内存地址或已释放内存空间的指针。

野指针的常见成因:

1.未初始化的指针。
2.指针指向的内存已释放:
int *p = (int *)malloc(sizeof(int));
free(p); // 释放内存
*p = 10; // 此时 ptr 是野指针,解引用非法
3.指针越界访问:
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
p += 5; // 指向数组末尾之后的内存,解引用非法
4.函数返回局部变量的地址:
int *fp() 
{int n = 10; // 局部变量,存储在栈上return &n; // 返回局部变量地址,函数返回后 num 的内存被释放
}
int *p = fp(); // ptr 是野指针

避免野指针:

1.初始化指针。

2.使用free()释放内存后,立即将指针置为NULL。

3.函数若需返回内存地址,应返回动态分配的内存(如malloc),或指向全局变量的指针。

int *fp() 
{int *p = (int *)malloc(sizeof(int)); // 动态分配内存*p = 10;return p; // 合法,调用者需负责释放
}

指针运算

指针可以进行哪些运算?

指针的算术运算主要和指针所指向的数据类型有关,编译器会根据数据类型来计算实际的内存偏移量。

1.指针与整数的加减:

指针 + 整数:指针向前偏移 整数倍的sizeof(数据类型)个字节。

例如,若int *p指向地址0x1000,那么p + 1指向0x1004(int 占四个字节)

int a[5] = {1, 2, 3, 4, 5};
int *p = a;           // p 指向 arr[0](地址 0x1000)
p = p + 2;              // p 指向 arr[2](地址 0x1008)

指针 - 整数:指针向后偏移整数倍的sizeof(数据类型)个字节。

2.指针与指针的相减:

当两个指针指向同一块连续内存区域(如数组元素)时,相减结果为他们之间的元素个数(而非字节差)。

int *p1 = &a[4];      // 指向 arr[4]
int *p2 = &a[1];      // 指向 arr[1]
int diff = p1 - p2;     // diff = 3(4 - 1 = 3 个元素)

3.指针的自增和自减:

前置、后置自增:指针指向下一个元素。

p++;                    // 等价于 p = p + 1,指向 arr[2]

前置、后置自减:指针指向前一个元素。

p--;                    // 等价于 p = p - 1,指向 arr[1]

4.指针可以通过关系运算符(<   <=   >   >=   ==   !=)进行比较。

1.指向同一块连续内存区域时(比如数组元素),比较的是指针的地址顺序。
int *p = a, *q = &a[3];
if (p < q)         // p 的地址小于 q 的地址,条件成立
{            printf("p在q的前面\n");
}
2.判断指针是否为空
int *p = NULL;
if (p == NULL)        // 指针未初始化,避免野指针解引用{      printf("ptr is null\n");
}

5.指针的间接访问运算 *

通过解引用运算符*来获取指针指向的内存值,或修改值。

int n = 10;
int *p = &n;
printf("%d\n", *p);     // 输出 10(访问 x 的值)
*p = 20;                 // 修改 x 的值为 20
printf("%d\n", n);       // 输出 20

6.指针的赋值运算

将一个指针的值(也就是地址),赋给另一个同类型的指针,使他们指向同一内存位置。

int a[5] = {1, 2, 3, 4, 5};
int *p1 = a;           // p1 指向 a[0]
int *p2 = p1 + 2;        // p2 指向 a[2]
p1 = p2;                 // p1 现在也指向 a[2]

如何比较两个指针?

比较两个指针的地址值

== :判断两个指针是否指向同一个地址

!= :判断两个指针是否指向不同地址。

>  <   :当指针指向同一数组元素时,表示两个指针的相对位置。

指针与数组

数组名和指针有什么关系?

1.大部分表达式中,数组的数组名会被自动转换成指向数组首元素的指针。

2.当数组名作为函数参数进行传参时,被视为指针。

3.当用sizeof运算符来获取数组整体大小时,不会被看作指针,而是整个数组。

而如果sizeof指针的话,就是指针变量本身的字节大小。

4.用&来取数组地址时,数组名代表的是整个数组,而不是指针。

如何通过指针访问数组元素?

1.数组名等价于指向首元素a【0】的指针。可以直接解引用 ,用*(a + i)来访问。

*(a + i)完全等价于a【i】。

2.定义一个指针变量p,让它指向数组首元素,然后p++或p--来访问数组元素。

指针数组和数组指针的区别是什么?

指针数组:

本质是一个数组,数组的每个元素都是指针。例如:

int  *a[5]              5个元素,每个元素都是int * 的指针。

因为[]的优先级要高于*,所以a会先与[]结合,表明是数组,每个元素都是指针。

数组指针:

本质是一个指针,该指针指向一个数组。例如:

int  (*p)[5]                         p是一个指针,指向包含五个int 类型元素的数组。

()改变了优先级,使p先与*结合了,变成了指针,指向的目标是一个数组。

指针与函数参数

如何将指针作为函数参数?

1.当声明了一个函数,其参数为int类型的指针,

然后我们要在主函数调用它并修改调用者提供的变量时,就需要取地址来传递。

// 函数声明:参数为 int 类型的指针
void fun(int *p) 
{*p = 100; // 通过指针修改指向的值
}int main() 
{int n = 50;printf("修改前:%d\n", n); // 输出:50fun(&n); // 传递 n 的地址(&n)printf("修改后:%d\n", n); // 输出:100return 0;
}

2.当处理数组时,则传递数组名,因为数组的数组名就是指向数组首元素的指针。

可以用一个for循环来打印全部的数组元素。

// 函数声明:参数为 int 类型数组的指针(等价于 int a[] 或 int *a)
void fun(int *a, int len) 
{int i = 0;    for (i = 0; i < len; i++) {printf("%d ", *(a + i)); // 等价于 a[i]}
}int main() 
{int a[] = {1, 2, 3, 4, 5};int len = sizeof(a) / sizeof(a[0]);fun(a, len); // 传递数组名(即首元素地址)return 0;
}

如何通过指针修改实参的值?

通过指针修改实参的值需要向函数传递实参的地址(指针),然后在函数内部通过解引用*指针来操作原始变量。

// 定义函数:通过指针修改传入的整数值
void fun(int *p) 
{// 解引用指针并赋值,直接修改原始变量*p = 100;
}int main() 
{int n = 10;printf("修改前:n = %d\n", n);    n  =  50// 传递 original 的地址(指针)给函数fun(&n);printf("修改后:n = %d\n", n);   n = 100return 0;
}

多级指针

什么是二级指针?请举例说明

二级指针是指向指针的指针,即该指针变量存储的是另一个指针变量的地址。一级指针指向普通变量,而二级指针则指向一级指针。

例如:           int  **pp      表示pp是一个二级指针,指向一个指向int类型的指针。

int main() 
{int n = 10;          // 普通变量,存储整数 10int *p = &n;         // 一级指针 p,存储 num 的地址int **pp = &p;         // 二级指针 pp,存储一级指针 p 的地址// 访问变量的值printf("num = %d\n", num);                // 直接访问:10printf("*p = %d\n", *p);                  // 通过一级指针访问:10printf("**pp = %d\n", **pp);              // 通过二级指针访问:10// 访问指针的地址printf("&num = %p\n", &num);              // num 的地址printf("p = %p, &p = %p\n", p, &p);       // p 存储的是 num 的地址,&p 是 p 本身的地址printf("pp = %p, &pp = %p\n", pp, &pp);   // pp 存储的是 p 的地址,&pp 是 pp 本身的地址// 通过二级指针修改变量的值**pp = 20;printf("修改后 num = %d\n", num);         // 输出:20return 0;
}

二级指针可以用与动态内存分配中的指针传递:

// 动态分配二维数组(行指针数组)
int r = 3, c = 4;int **pp = (int **)malloc(r * sizeof(int *));for (int i = 0; i < r; i++) 
{pp[i] = (int *)malloc(c * sizeof(int));
}

通过二级指针,每一行的长度可以不相同(如锯齿数组)。灵活性更高。

如何定义和使用二级指针?

定义:

定义二级指针时,要使用两个**,比如int **pp

使用:

可以两次解引用,来访问和修改一级指针所指向的变量的值。

在函数传参时,可以让函数修改一级指针的值:

void fun(int **pp)
{static int n = 30;*pp = &n;
}int main() 
{int n = 10;int *p = &num;  // 一级指针,指向 numint **pp = &p;  // 二级指针,指向一级指针 ptrprintf("修改前 ptr 指向的值: %d\n", *p);fun(pp);printf("修改后 ptr 指向的值: %d\n", *p);return 0;
}

需要注意的是,在 fun 函数中,n 是一个局部变量,当函数执行完毕后,n 的内存会被释放。因此,在 main 函数中输出修改后 ptr 指向的值时,可能会得到一个不确定的值,这是因为 ptr 指向的内存已经被释放。因此需要在前面加上static。使用静态变量,确保其生命周期贯穿整个程序。

函数指针

什么是函数指针?如何定义和使用函数指针?

函数指针:

是一种指向函数的指针变量,它存储了函数在内存中的入口地址。

定义:

基本格式为:返回类型  (*指针变量名)(参数列表)

例如:int (*fun)(int a)

使用:

1.函数指针的赋值:

函数指针需要指向一个具体的函数,直接使用函数名即可获取函数的地址。:

// 假设存在目标函数:int my_fun();
func_ptr = my_fun; // 或 &my_fun(取地址符可选,函数名本身会隐式转换为地址)

2.通过函数指针调用函数:

使用(*指针变量名)(参数):

int  result = 0;result = (*fun)(); // 调用 fun 指向的函数

如何通过函数指针调用函数?

// 定义一个普通函数
int add(int a, int b) 
{return a + b;
}int main() 
{int result = 0;// 声明函数指针,其指向的函数返回值为 int,参数为两个 intint (*fun)(int a, int b);// 将函数指针指向具体的函数(这里指向 add 函数)fun = add;// 通过函数指针调用函数result = fun(3, 5);printf("通过函数指针调用的结果:%d\n", result);return 0;
}

如何定义和使用指向函数的指针数组?

// 定义三个不同的函数,用于后续赋值给函数指针
int add(int a, int b) 
{return a + b;
}int sub(int a, int b) 
{return a - b;
}int mul(int a, int b) 
{return a * b;
}int main() 
{int x = 10, y = 5;// 定义指向函数的指针数组,每个元素指向返回int、参数为两个int的函数int (*fun[3])(int a, int b);// 将函数地址赋值给数组元素(函数名即为函数地址)fun[0] = add;fun[1] = sub;fun[2] = mul;// 通过函数指针数组调用函数printf("add: %d\n", func[0](x, y));       // 调用 add 函数printf("subtract: %d\n", func[1](x, y));    // 调用 sub 函数printf("multiply: %d\n", func[2](x, y));    // 调用 mul 函数return 0;
}

这种指针数组可以通过修改数组元素就切换不同的函数功能,提高代码拓展性。

回调函数

什么是回调函数?回调函数的应用场景有哪些?

定义:

简单来说,回调函数是“被其他函数调用的函数”,当某段代码需要在特定事件发生时、或完成某个操作后调用一个自定义函数时,会将这个自定义函数的指针作为参数传递进去,供其在适当的时候调用。

回调函数本质是函数指针。

应用场景:
// 回调函数:定义 int 类型数组的升序比较规则
int compare(const void* a, const void* b) 
{return (*(int*)a - *(int*)b);
}int main() 
{int arr[] = {3, 1, 2};qsort(arr, 3, sizeof(int), compare); // 传递回调函数return 0;
}

比如C语言标准库里的qsort函数是一个通用排序函数,它不知道我要排序的数据是什么类型(整数、结构体、字符串等),也不知道我希望按什么规则去进行排序(升序、降序、自定义逻辑),因此。qsort函数需要我通过回调函数告诉它两件事:

1.排序顺序是什么?

2.数据类型的大小?

那么就可以写一个compare函数,这个就是传递给qsort的回调函数。

函数的参数选择const  void*  。这是因为:

void * 是通用指针,可以接收任何数据类型(因为qsort是通用函数,可能有很多种不同的数据类型)

const表示不修改传入的数据,遵循只读的规则。

当qsort比较两个元素时,会把这两个元素的地址强制转换为const void*传递给compare。

然后在回调函数里写排序逻辑,比如:

return (*(int*)a - *(int*)b);

表示当 a 的值(如 3)大于 b 的值(如 1)时,返回 3-1=2(正数),表示 a 应在 b 后面。

同样的,当 a 的值(如 1)小于 b 的值(如 2)时,返回 1-2=-1(负数),表示 a 应在 b 前面。

整个代码执行流的主动权在qsort手上,qsort来决定什么时候调用compare回调函数。

compare告诉qsort“怎么比”,而qsort负责“怎么排”。

指针数组与数组指针

什么是指针数组?如何定义和使用指针数组?

定义:

指针数组是一个数组,其每一个元素都是指针。基本格式为:

数据类型  *数组名[数组长度]             比如int  *a[3]

使用:

1.指向整数的指针数组:
int n1 = 10, n2 = 20, n3 = 30;
int *p[3];  // 定义一个包含 3 个 int 类型指针的数组// 初始化指针数组,使其指向不同的整数
p[0] = &n1;
p[1] = &n2;
p[2] = &n3;
2.指向字符的指针数组(常用与字符串):
char *p[3] = 
{"Hello",   // 指针指向字符串常量 "Hello" 的首地址"World",   // 指针指向字符串常量 "World" 的首地址"C Language"
};
3.通过数组下标访问指针数组的元素,再通过解引用*来获取指向的值:
// 示例 1 中访问整数的值
printf("Value of num1: %d\n", *p[0]);  // 输出 10// 示例 2 中访问字符串
printf("First string: %s\n", p[0]);     // 输出 Hello

注意到:

示例1中,p[0]的类型是int *(指向int 的指针),需求是获取指针指向的整数的值(即n1的值10)。而解引用的作用,*p[0]  表示“访问 p[0] 所指向的内存地址的值”,printf  中的%d 需要一个int 类型的参数,而p[0]是int * 类型,因此需要解引用。

示例2 中,p[0]的类型是char * (指向char 的指针),但这里它指向字符串“Hello”的首字符“H”的地址,而字符串本质上是字符数组,由char 类型的连续内存空间组成,以 “\0” 结尾。

printf的 %s 格式符要求传入一个char * 类型的参数(指向字符串首字符的指针),它会自动从该指针开始读取字符,直到遇到 ‘\0’ ,

示例2的 p[0] 本身就是char *类型,满足%s 的参数要求,因此无需解引用。

如果加上解引用,则会得到首字符 ‘H’ ,后续字符会读不到。

4.作为函数参数
void fun(char *a[], int len) 
{int i = 0;for (i = 0; i < len; i++) {printf("%s\n", a[i]);}
}// 调用函数
fun(p, 3);
5.动态内存分配
int n = 5;
int i = 0;
int *a[n];  // 定义指针数组for (i = 0; i < n; i++) 
{a[i] = (int *)malloc(sizeof(int));  // 为每个指针分配内存*a[i] = i + 1;  // 赋值
}// 访问数据
printf("Third value: %d\n", *a[2]);  // 输出 3// 释放内存
for (i = 0; i < n; i++) 
{free(a[i]);
}

什么是数组指针?如何定义和使用数组指针?

定义:

数组指针是一个指向数组的指针变量,存储的是整个数组的起始地址。

数组指针的类型需要同时指定数组元素的类型和数组的长度,本质是指向一个特定长度数组的指针。

基本格式为:          数据类型    (*指针变量名)[数组长度]

例如:        

int a[5] = {1, 2, 3, 4, 5};        // 定义一个 int 类型的数组
int (*p)[5];                       // 定义数组指针,指向长度为 5 的 int 数组

初始化:

将数组指针指向同一类型、同长度的数组,直接使用数组名(数组名表示数组首地址):

ptr = &a;  // 或直接写 ptr = arr;(因为数组名隐式转换为指向数组首元素的指针,但数组指针需要指向整个数组,此处 &a 更明确)

使用:

通过数组指针访问元素时,需要先解引用指针得到数组,在通过下标索引访问元素:

printf("a[2] = %d\n", (*p)[2]);  // 等价于 a[2],输出 3

指针函数与函数指针

什么是指针函数?如何定义和使用指针函数?

定义:

指针函数是一种返回值为指针(即内存地址)的函数,本质是函数,只是返回的类型是指针。

使用:

1.返回字符串指针的函数:
// 指针函数:返回一个指向 char 类型的指针(字符串)
char *fun() 
{// 动态分配内存(需手动释放)char *str = (char *)malloc(100 * sizeof(char));strcpy(str, "Hello, World!");return str; // 返回动态分配的内存地址
}int main() 
{char *p = fun(); // 调用指针函数printf("%s\n", p);      // 输出字符串free(p);                 // 释放内存,避免内存泄漏return 0;
}
2.返回指向数组元素的指针:
// 指针函数:返回指向 int 数组中最大值元素的指针
int *fun(int a[], int size) 
{int *max = &a[0]; // 初始假设第一个元素是最大值int i = 0;for (i = 1; i < size; i++) {if (a[i] > *m) {max = &a[i]; // 更新最大值指针}}return max; // 返回最大值元素的地址
}int main() 
{int n[] = {10, 20, 30, 15, 25};int *max = fun(n, 5);printf("最大值:%d\n", *max); // 输出 30return 0;
}

指针函数和函数指针的区别是什么?

特征指针函数                                函数指针                                
核心函数,返回指针指针,指向函数
声明int  *a()int  (*a)()
用途返回内存地址(动态分配内存)存储函数地址(回调函数)
优先级函数名优先级高于*,先调用函数()优先级高于*,需括起来明确是指针

相关文章:

  • STM32单片机入门学习——第43节: [12-3] 读写备份寄存器实时时钟
  • 无需训练的具身导航探索!TRAVEL:零样本视觉语言导航中的检索与对齐
  • 山东科技大学人工智能原理考试回忆复习资料
  • python基础知识点(1)
  • 猫咪如厕检测与分类识别系统系列【十二】猫咪进出事件逻辑及日志优化
  • 【Datawhale AI春训营】Java选手初探数据竞赛
  • 【对Linux文件权限的深入理解】
  • 有源低通滤波器 sallen-key低通滤波器原理与计算
  • 《2025最新Java面试题全解析:从基础到高并发架构设计》
  • 速查手册:TA-Lib 超过150种量化技术指标计算全解 - 2. Momentum Indicators(动量指标)
  • 超大文件处理——文件强制切割:突破存储传输限制,提升数据处理效能—星辰大文化术——未来之窗超算中心
  • PKI 公钥基础设施
  • STM32学习笔记汇总
  • JavaWeb 课堂笔记 —— 13 MySQL 事务
  • 解决win10执行批处理报编码错误
  • Nodejs数据库单一连接模式和连接池模式的概述及写法
  • Meteonorm8-免费使用教程(详细教程-免费)
  • RK3506-rtlinux
  • Linux系统之部署TestNet资产管理系统
  • 豆瓣图书数据采集与可视化分析(一)- 豆瓣图书数据爬取
  • 上海推出平台算法治理合规指引:不得“静默推荐”,算法应用向上向善
  • 谁在向张福生行贿?
  • 牛市早报|李强:在一些关键的时间窗口,推动各方面政策措施早出手、快出手
  • 专业纯粹,安静温暖,上海古籍书店明日重新开张
  • 贵阳市消防救援支队原支队长李世永受审,为谋提拔给中管干部送25万
  • 武汉一超高层住宅顶楼违建成“不死小强”,相关部门回应