一、C语言指针介绍
1.1、基本概念
在C语言中,指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。当一个变量被声明为指针类型时,编译器将为该变量分配一个可以存储内存地址的空间。指针在C语言编程中非常重要,它们用于动态内存分配、函数参数传递、数据结构(如链表和树)等。
1.2、指针介绍
(1)指针是一个变量,它存储的是另一个变量的内存地址,而不是直接存储数据值。
(2)指针是有类型的,指针的类型决定了“指针的+-整数的步长”,以及解引用时候的权限。
(3)指针通过取地址运算符(&)来获取变量的地址,而箭头运算符(->)用于访问指向结构体或类的指针成员,它不能直接用于获取变量的地址。
(4)指针之间存在运算,多级指针其实就是指向指针的指针。
二、C语言指针基础
2.1、指针的定义
以下是常见的指针定义,区分其含义以便更好的理解指针(优先级问题)。 - int p; //一个普通的整型变量
- int *p; //P先与*结合,说明P是一个指针,然后再与int结合,说明指针指向型为int型.所以P是一个返回整型数据的指针
- int **p; //(多级指针略)P先与*结合,再与*结合,然后再与int结合,说明该指针所指向的元素是整型数据
- int p[4]; //P先与[]结合,然后与int 结合,说明p是一个整型数组。
- int *p[4]; //P先与[]结合,因为其优先级比*高,然后再与*结合,然后再与int 结合,所以P是一个由返回整型数据的指针所组成的数组
- int (*p)[4]; //P先与*结合,然后再与[]结合,然后再与int 结合,所以P是一个指向由整型数据组成的数组的指针
- int (*p)(int); //P先与指针结合,然后与()结合,然后再与()里的int结合,所以P是一个指向有一个整型参数且返回类型为整型的函数的指针
复制代码
注意:每定义一个指针,都应该思考这个指针的类型是什么?指针指向的类型是什么?该指针的值是什么?(指向了哪里?)
2.2、指针的基本运算
(1)地址指向运算 - #include <stdio.h>
- int main() {
- int numb=1;
- int *p;
- p=&numb;//加地址符,指向numb
- (*p)++;
- printf("%d %d\n",numb,*p);
- int numb1[5]={11,22,33,44,55};
- int *p1;
- p1=numb1;//数组不需要加地址符
- for(int i=0;i<5;i++){//p1的指向会变
- printf("%d ",*p1);
- p1++;
- }
- p1=p1-5;//让p1重新指向开始的位置
- for(int i = 0; i < 5; i++) {//p1的指向不会变
- printf("%d ", *(p1 + i));
- }
-
- char a[20]="You are a girl";
- int *ptr=(int *)a;
- ptr+=5;
- printf("\n%d",*ptr);//随机输出,指针类型不能用于强制转化
- return 0;
- }
复制代码
(2)多级指针运算 - #include <stdio.h>
- int main() {
- int a = 10;
- int *p1 = &a;
- int **p2 = &p1;
- int ***p3=&p2;
- printf("%d\n", a);
- printf("%d\n", *p1);//p1指向a
- printf("%d\n", **p2);//p2指向p1,则三者值都一样
- printf("%d\n", ***p3);//p3指向p2,四者值都一样
- int b=20;
- p1=&b;//p1指向b
- printf("%d %d %d\n",*p1,**p2,***p3);//p1指向改变,对应p2,p3都变
- b=30;
- printf("%d %d %d\n",*p1,**p2,***p3);//b数值改变,p1,p2,p3都变
- int *k=&a;
- p2=&k;
- printf("%d %d\n",*p1,***p3); //p2指向改变,p3变,p1不变
- return 0;
- }
复制代码
(3)指针间运算 - #include <stdio.h>
- int main() {
- int arr[] = {10, 20, 30, 40, 50};
- int *p1 = arr; // 指向数组的第一个元素
- int *p2 = arr + 2; // 指向数组的第三个元素
- // 指针加法
- printf("p1 + 1 = %p\n", p1 + 1); // 移动到下一个元素
- // 指针减法
- printf("p2 - 1 = %p\n", p2 - 1); // 移动到前一个元素
- // 指针差值
- printf("p2 - p1 = %ld\n", p2 - p1); // 计算两个指针之间的元素个数
- // 指针自增
- p1++;
- printf("p1++ = %p\n", p1); // 自增后指向下一个元素
- // 指针自减
- p2--;
- printf("p2-- = %p\n", p2); // 自减后指向前一个元素
- // 指针比较
- if (p1 == p2) {
- printf("p1 == p2\n");
- } else {
- printf("p1 != p2\n");
- }
- // 指针间接引用
- printf("*p1 = %d\n", *p1); // 访问指针p1所指向的值
- return 0;
- }
复制代码
2.3、const修饰指针
(1) 指向常量的指针
其中ptr是一个指向const int的指针。你不能通过ptr修改它所指向的值,但可以改变ptr本身,使其指向其他地址。 - const int value = 10;
- const int *ptr = &value;
- // *ptr = 20; // 错误:不能通过ptr修改value的值
- int anotherValue = 30;
- ptr = &anotherValue; // 可以改变ptr指向的地址
复制代码
(2)常量指针
其中 ptr是一个常量指针。你可以通过ptr修改它所指向的值,但不能改变ptr本身,使其指向其他地址。 - int value = 10;
- int *const ptr = &value;
- int anotherValue=30;
- *ptr = 20; // 可以通过ptr修改value的值
- // ptr = &anotherValue; // 错误:不能改变ptr指向的地址
复制代码
(3)指向常量的常量指针
其中 ptr是一个指向const int的常量指针。你既不能通过ptr修改它所指向的值,也不能改变ptr本身,使其指向其他地址。 - const int value = 10;
- int anotherValue=30;
- const int *const ptr = &value;
- // *ptr = 20; // 错误:不能通过ptr修改value的值
- // ptr = &anotherValue; // 错误:不能改变ptr指向的地址
复制代码
三、C语言指针的应用
3.1、指针内存分配问题
在C语言中,指针的内存分配主要有两种方式:静态分配和动态分配。
(1)静态分配
静态分配是在编译时完成的,内存空间在程序运行期间是固定的。通常用于局部变量和全局变量。例如: - int value = 10; // 静态分配
- int *ptr = &value; // 指针指向静态分配的内存
复制代码
(2)动态分配
动态分配是在程序运行时完成的,使用标准库函数malloc、calloc或realloc来分配内存,使用free来释放内存。 - int *ptr = (int *)malloc(sizeof(int)); // 动态分配
- if (ptr != NULL) {
- *ptr = 10; // 使用动态分配的内存
- }
- free(ptr); // 释放动态分配的内存
复制代码
动态内存分配函数:
malloc(size_t size):分配指定大小的内存块,返回指向该内存块的指针。
calloc(size_t num, size_t size):分配指定数量的内存块,并初始化为零。
realloc(void *ptr, size_t size):调整之前分配的内存块的大小。
free(void *ptr):释放之前分配的内存块。
扩展:计算本地电脑可给指针最多分配多少内存,代码如下: - #include <stdio.h>
- #include<stdlib.h>
- int main() {
- void *p;
- int cnt=0;
- while((p=malloc(100*1024*1024))) {
- cnt++;
- }
- printf("电脑可分配内存:%d00MB",cnt);
- return 0;
- }
复制代码
3.2、指针的常见用途
(1)动态内存分配
C语言使用指针进行动态内存分配,例如malloc,calloc,realloc和free函数。这些函数返回一个指向已分配内存的指针,或者在释放内存时使用指针。
举例: - #include <stdio.h>
- #include <stdlib.h>
- int main() {
- /* 使用 malloc 分配内存 */
- int *ptr = (int*) malloc(5 * sizeof(int));
- if(ptr == NULL) {
- printf("没有成功分配内存(Memory not allocated)\n");
- return 0;
- }
- else {
- printf("成功分配内存(Memory successfully allocated)\n");
- /* 使用这块内存 */
- for(int i = 0; i < 5; i++)
- ptr[i] = i + 1;
- /* 打印数组元素 */
- for(int i = 0; i < 5; i++)
- printf("%d, ", ptr[i]);
- }
- /* 使用 realloc 重新分配内存 */
- ptr = (int*) realloc(ptr, 10 * sizeof(int));
- if(ptr == NULL) {
- printf("没有成功分配内存(Memory not allocated)\n");
- return 0;
- }
- else {
- printf("\n成功分配内存(Memory successfully allocated).\n");
- /* 使用这块内存 */
- for(int i = 5; i < 10; i++)
- ptr[i] = i + 1;
- /* 打印数组元素 */
- for(int i = 0; i < 10; i++)
- printf("%d, ", ptr[i]);
- }
- free(ptr); /* 释放内存 */
- return 0;
- }
复制代码
(2)数组、字符串和结构体
指针用于处理数组和字符串。例如,字符串在C语言中表示为字符数组,通常使用字符指针处理。结构体指针用于动态分配结构体并访问其成员。
举例: - #include <stdio.h>
- int main() {
- int arr[5] = {1, 2, 3, 4, 5};
- int *p1 = arr; // 指向数组的指针
- for(int i = 0; i < 5; i++) {
- printf("%d ", *(p1 + i)); // 使用指针访问数组元素
- }
- printf("\n");
- char str[] = "Hello, World!";
- char *p2 = str; // 指向字符串的指针
- while(*p2 != '\0') {
- printf("%c", *p2); // 使用指针访问字符串中的字符
- p2++;
- }
- return 0;
- }
复制代码
(3)数据结构
许多数据结构,如链表,树,图等,都依赖于指针。这些数据结构的节点通常包含指向其他节点的指针。
举例: - #include <stdio.h>
- #include <stdlib.h>
- // 定义链表节点
- typedef struct Node {
- int data;
- struct Node* next;
- } Node;
- // 创建新节点
- Node* createNode(int data) {
- Node* newNode = (Node*)malloc(sizeof(Node));
- if(newNode == NULL) {
- printf("不能创建新结点(Unable to create a new node)\n");
- exit(0);
- }
- newNode->data = data;
- newNode->next = NULL;
- return newNode;
- }
- // 插入新节点到链表的末尾
- void insertNode(Node** head, int data) {
- Node* newNode = createNode(data);
- if(*head == NULL) {
- *head = newNode;
- return;
- }
- Node* temp = *head;
- while(temp->next != NULL) {
- temp = temp->next;
- }
- temp->next = newNode;
- }
- // 打印链表
- void printList(Node* head) {
- Node* temp = head;
- while(temp != NULL) {
- printf("%d -> ", temp->data);
- temp = temp->next;
- }
- printf("NULL\n");
- }
- int main() {
- Node* head = NULL;
- insertNode(&head, 1);
- insertNode(&head, 2);
- insertNode(&head, 3);
- printList(head);
- return 0;
- }
复制代码
(4)函数参数的传递
如果想在函数中修改变量的值,或者传递大型数据结构(如数组或结构)而不复制整个结构,那么可以使用指针。这被称为“通过引用传递”。 - #include <stdio.h>
- //通过指针接收一个整数,并将其值加一
- void addOne(int* ptr) {
- (*ptr)++; // 增加指针ptr所指向的值
- }
- int main() {
- int num = 0;
- printf("原始数据: %d\n", num);
- addOne(&num);
- printf("修改后: %d\n", num);
- return 0;
- }
复制代码
(5)函数指针
可以创建指向函数的指针,这在编写可插入函数、回调函数或者函数表等高级编程任务时非常有用。
举例:用 C语言编写程序,定义两个函数 mean 和 square 分别计算三个数的平均值和平方和,在主函数中输入三个数并调用它们,要求使用函数指针,当用户输入1时求平均值,输入2时求平 方和。 - #include <stdio.h>
- double mean(int a, int b, int c){
- return (a+b+c)/3.0;
- };
- int square(int a, int b, int c){
- return(a*a+b*b+c*c);
- };
- int main() {
- int choice;
- int num1, num2, num3;
- double result;
- double (*func_ptr)(int, int, int);//函数指针
- printf("请输入三个整数:");
- scanf("%d %d %d", &num1, &num2, &num3);
- printf("请选择操作:\n");
- printf("1. 求平均值\n");
- printf("2. 求平方和\n");
- scanf("%d", &choice);
- if (choice == 1) {
- func_ptr = mean;
- } else if (choice == 2) {
- func_ptr = square;
- } else {
- printf("无效选择。\n");
- return 1;
- }
- result = func_ptr(num1, num2, num3);
- if (choice == 1) {
- printf("三个数的平均值为:%.2f\n", result);
- } else if (choice == 2) {
- printf("三个数的平方和为:%.2f\n", result);
- }
- return 0;
- }
复制代码
|