C语言基础开发入门系列(八)C语言指针的理解与实战

文章目录

前言

这章我们主要介绍C语言中指针的概念。C语言指针是C语言中一种非常重要的数据类型,它存储的是内存地址。通过指针,我们可以直接操作内存,这使得C语言在系统编程、硬件操作等方面非常强大。

一、C语言指针介绍

指针是一个变量,其值为另一个变量的内存地址,简单来说,指针就是地址的变量化。指针是C语言中一个非常重要且强大的特性,它允许直接操作内存地址。理解指针是掌握C语言的关键。下面将从指针的基本概念、声明、初始化、运算、指针与数组等方面进行详细讲解。

1.指针的声明

指针在使用前,必须先声明,指针的声明需要指定指针所指向的数据类型,指针的定义格式如下:

*数据类型 指针变量名;

C语言指针的声明写法如下:

c 复制代码
int *p;      // 指向整型的指针
char *c;     // 指向字符型的指针
float *f;    // 指向浮点型的指针

2.指针的初始化

指针变量在使用之前必须初始化,否则它可能指向一个随机的内存地址,导致未定义行为。指针的初始化通常是取另一个变量的地址。示例如下:

c 复制代码
int var = 10;//定义整型变量
int *p;//申明指针
p= &var;   // p 指向变量 var 的地址,&是取地址

4.指针数据的访问

通过解引用运算符:*,实现指针访问或修改其指向的内存地址中存储的值。

指针访问举例:

c 复制代码
int var = 10;
int *p = &var;
printf("%d", *p);   // 输出 10
*p = 20;            // 通过指针修改变量 var 的值
printf("%d", var);  // 输出 20

5.指针的运算

指针支持有限的算术运算:递增、递减、加整数、减整数和指针相减。在C语言中,数组名在大多数情况下表示数组首元素的地址。

c 复制代码
int arr[] = {10, 20, 30};
int *p = arr;   // p 指向数组的首元素地址
printf("%d", *p);   // 输出 10
p++;                // 指向下一个整型元素,即 arr[1]
printf("%d", *p);   // 输出 20
c 复制代码
int arr[] = {10, 20, 30, 40};
int *p = arr;     // 等价于 int *p;p = &arr[0];
p = p + 2;        // 现在 p 指向 arr[2]
printf("%d", *p); // 输出 30
c 复制代码
int arr[] = {1, 2, 3};
int *p = arr;   // 等价于 int *p;p = &arr[0];
// 通过指针访问数组元素
*(p + 1) = 10;  // 等价于 arr[1] = 10;

通过指针,可以在函数内部修改外部变量的值。

c 复制代码
//实现a,b两个地址的数值交换
void swap(int *a, int *b) 
{
    int temp = *a;//temp为a指针的内容
    *a = *b;//a指针指向的内容改成b指针指向的值
    *b = temp;//b指针指向的值改成temp的内容
}

int main(void) 
{
    int x = 10, y = 20;
    swap(&x, &y);//交换x,y的值
    printf("x=%d, y=%d", x, y); // 输出 x=20, y=10
    return 0;
}

6.函数指针

函数指针是指向函数的指针变量,它存储函数的地址,通过这个指针可以调用相应的函数。基本语法返回类型 (*指针变量名)(参数列表);。

c 复制代码
#include <stdio.h>
int add(int a, int b) //定义两个参数相加的函数
{
    return a + b;
}
int (*operation)(int, int);  // 声明函数指针
int main(void) 
{
    operation = add; // 指针指向add函数的地址
    printf("10 + 5 = %d\n", operation(10, 5));//执行函数指针调用,实现两个数值相加
    return 0;
}

7.通过数组求累加和学习C语言指针编程

这个简单例程完整展示了一维数组指针的核心概念,是理解C语言指针基础的最佳起点。通过这个例子,你可以清楚地看到指针如何与数组交互,以及如何使用指针来操作数组数据。

c 复制代码
#include <stdio.h>
#define ITM_PORT8(n)         (*(volatile unsigned char *)(0xe0000000 + 4*(n)))//Keil5环境调用printf打印需要的定义
int fputc(int ch, FILE *f)//Keil5环境调用printf打印需要的接口函数
{
    ITM_PORT8(0) = ch;
    return ch;
}



// 计算数组元素的和,返回的累加和就是1字节的数据,如果超出1字节,依旧是取1字节的数据
unsigned char calculate_sum(unsigned char*arr, unsigned char size) 
{
    unsigned char sum = 0;
    for (unsigned char i = 0; i < size; i++) 
	  {
        sum += *(arr + i); // 使用指针访问元素
    }
    return sum;
}


unsigned char numbers[] = {2,5,8,3,9,1,7,10,15,20};// 定义数组,数组内容是10字节的数据
unsigned char *ptr;//声明指针
int main(void) 
{
	unsigned char size = sizeof(numbers) / sizeof(numbers[0]);//定义变量size,size是计算数组中元素的个数
	printf("指针赋值前,指针指向的地址 %d\n", ptr);
	ptr = numbers;//初始化指针指向数组首地址	
	printf("指针赋值后指向地址0x%x\n", ptr);
	printf("数组numbers起始地址0x%x\n", &numbers[0]);	
	printf("数组numbers内容:\n");//准备打印数组内容

	  for (int i = 0; i < size; i++) 
    {
        printf("%d ", *(ptr + i));//*(ptr + i) 表示取出指针ptr向后移动i个元素的该位置的值,这个循环就是打印数组内每个元素内容
    }
    printf("\n");//打印换行符
		
		for (int i = 0; i < size; i++) 
    {
       *(ptr + i)=i;//修改数组里的每个元素值,每个元素值是跟i相关,第i个,数值就是i
    }
		
		printf("修改后的数组numbers内容:\n");//准备打印数组内容
		for (int i = 0; i < size; i++) 
    {
        printf("%d ", *(ptr + i));//*(ptr + i) 表示取出指针ptr向后移动i个元素的该位置的值,这个循环就是打印数组内每个元素内容
    }
    printf("\n");//打印换行符

	unsigned char sum = calculate_sum(numbers, size);//定义变量sum,调用函数计算数据累加和,函数的形参是指针,所以实参numbers需要是地址值
    printf("修改后的数组元素累加和: %d\n", sum);
    return 0;
}

调试上面的代码,先了解指针声明后指向的地址与赋值后的指向地址。指针声明后指向的地址是0,指针赋值后指向的地址是0x20000008,数组numbers的起始地址也是0x20000008。ptr = numbers;这句代码就是让指针ptr指向数组的起始地址。

如下图所示,我们查看内存空间,数组numbers的起始地址是0x20000008,也就是numbers[0]的地址,然后数组内容如下图顺序存储,跟定义的内容一致。

如下图所示,程序运行完打印指针指向的数组的内容,打印的内容与数组定义的数据一致,所以通过指针读取内容正确。

如下图所示,程序运行完打印修改后的数组内容,打印的内容与修改后的数据一致,所以通过指针对数组的内容进行修改正确。

如下图所示,程序运行完打印累加的计算结果内容,打印的内容与数组内所有数值相加的值一致,说明calculate_sum函数调用的没问题,实参传递也没问题。

8.指针常见错误和注意事项

指针是C语言中最强大但也最容易出错的地方,未初始化的指针,数组越界访问等问题。

c 复制代码
// 1. 未初始化的指针
int *p;
*p = 10;  // 错误!

// 2. 空指针引用
int *p = NULL;
*p = 10;  // 错误!

//数组越界访问
void array_bounds_demo() 
{
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    // 越界访问
    for (int i = 0; i <= 5; i++) 
    {  // 应该是 i < 5
        printf("%d ", *(ptr+i));  // ptr[5] 越界!,数组总共只有5个元素,*(ptr+5)是第6个元素,越界了
    }
}

总结

多写代码,多画内存图,在纸上画出变量、指针和它们地址之间的关系,是理解指针最有效的方法。从简单的例子开始,逐步尝试更复杂的应用,如动态数组、字符串处理、函数指针和链表等。

相关推荐
是苏浙2 小时前
零基础入门C语言之文件操作
c语言·开发语言
盈电智控2 小时前
体力劳动反而更难被AI取代?物联网科技如何守护最后的劳动阵地
开发语言·人工智能·python
隔壁阿布都2 小时前
Spring Boot中的Optional如何使用
开发语言·spring boot·python
小龙报2 小时前
《C语言疑难点 --- C语内存函数专题》
c语言·开发语言·c++·创业创新·学习方法·业界资讯·visual studio
国服第二切图仔3 小时前
Rust开发实战之简单游戏开发(piston游戏引擎)
开发语言·rust·游戏引擎
ii_best3 小时前
安卓/IOS工具开发基础教程:按键精灵一个简单的文字识别游戏验证
android·开发语言·游戏·ios·编辑器
Shylock_Mister3 小时前
ESP32堆栈空间优化全攻略
c语言·嵌入式硬件·物联网
草莓熊Lotso3 小时前
C++ 继承特殊场景解析:友元、静态成员与菱形继承的底层逻辑
服务器·开发语言·c++·人工智能·经验分享·笔记·1024程序员节
诗句藏于尽头3 小时前
电脑使用软件控制本机屏和外接屏失效问题及解决
开发语言