一、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) 指向常量的指针
const int *ptr;
其中ptr是一个指向const int的指针。你不能通过ptr修改它所指向的值,但可以改变ptr本身,使其指向其他地址。
const int value = 10;
const int *ptr = &value;
// *ptr = 20; // 错误:不能通过ptr修改value的值
int anotherValue = 30;
ptr = &anotherValue; // 可以改变ptr指向的地址
(2)常量指针
int *const ptr;
其中 ptr是一个常量指针。你可以通过ptr修改它所指向的值,但不能改变ptr本身,使其指向其他地址。
int value = 10;
int *const ptr = &value;
int anotherValue=30;
*ptr = 20; // 可以通过ptr修改value的值
// ptr = &anotherValue; // 错误:不能改变ptr指向的地址
(3)指向常量的常量指针
const int *const ptr;
其中 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;
}