C语言学习笔记——指针

1.取地址运算:&运算符取得变量的地址

1.1.变量的地址

  • 计算机的内存是以字节为单位的连续存储空间,每个字节都有一个编号,可以根据一个内存单元的编号准确的找到这个内存单元,这些编号称为地址,内存中地址的编号都是连续的

1.2.指针

  • 通常把变量的地址称为该变量的指针,即指针是一个地址,是一个常量

1.3.取地址符

  • 因为指针变量p中储存的是地址,因此如果p指向变量a,需要在a前面加一个取地址符&(获得变量的地址,它的操作数必须是变量),p=&a;,含义是指针p指向a的存储单元/地址,不能将一个整型变量赋值给一个指针变量,指针变量指向的是地址。
  • 获得变量的地址,它的操作数必须是变量
  • int i; printf(“%x”,&i);
  • 地址的大小是否与int相同取决于编译器
  • int i; printf(“%p”,&i);
  • 取地址用%p

2.指针:指针变量就是记录地址的变量

2.1.指针变量

  • 概念:C语言中允许用变量存放地址。如果一个变量专门用来存储其他变量的地址,那么这个变量就称为指针变量。严格的说,一个指针是一个地址,为一个常量,而一个指针变量的内容可以根据需要存放某个变量的地址,是可以变化的,是一个变量
  • 普通变量的值是实际的值
  • 指针变量的值是具有实际值的变量的地址
  • 定义:类型说明符 *指针变量名;
  • 初始化:int a, *p=&a; 或 int a; int *p; p=&a; 指针p指向a的存储单元/地址
  • *在不同位置有不同含义:可作为算术运算符乘号定义指针,如果出现在表达式中表示指针变量所指向的变量,即取内容运算符,取的是指针变量所指存储单元的值

3.指针的使用

3.1.指针的使用

  • 返回多个结果

  • 函数返回运算的状态,结果通过指针返回

4.指针与数组

4.1.指针和一维数组

  • C语言规定,数组名代表数组的起始地址,注意是地址!!!,等价于&a[0]a+i代表数组元素a[i]的地址,等价于&a[i]。例:
  • int a[5],  *p=a;           注意:a前面没有&符号,因为数组名a代表数组的起始地址。
  • int a[5],  *p=&a[0];   注意:a[0]前有&符号,因为a[0]代表元素不代表地址。

4.2.通过指针引用数组元素

  • 数组a中任一元素a[i]的地址可以用一下几种方式表示:&a[i]     a+i          p+i
  • 数组a中任一元素a[i]可以用一下几种方式表示:           a[i]       *(a+i)      *(p+i)    p[i]
  • 引用步骤:

                定义指针和数组,例:int *p, a[10];

                指针指向数组,例:p=a;p=&a[0];,即指向数组首地址

                通过指针引用数组元素

  • 注意:数组名是一个常量,不允许自增自减,但数组名可以加上或减去一个整数值,并把结果赋给指针变量。

4.3.指针和二维数组

  • 对于二维数组,a[i]代表第i行的首地址,则数组元素a[i][j]的地址可以由a[i]+j得到。
  • 对二维数组int a[i][j]:
  • a表示二维数组的首地址,即第0行的首地址。
  • a+i表示第i行的首地址。
  • *(a+i)  a[i]  &a[i][0],表示第i行第0列元素的地址。
  • 下标表示:a[i]+j   指针表示:*(a+i)+j,表示第i行第j列元素的地址。
  • 下标表示:*(a[i]+j)  指针表示: *(*(a+i)+j)    a[i][j],表示第i行第j列的元素。

4.4.字符指针与字符数组

  • 字符串实际上就是每个元素都是字符一维数组,最后一个元素用'\0'作为结束标志,通常用*ps是否等于'\0'判断字符串的结束位置
  • 字符指针:char *ps ="We are students" 用一个字符指针指向一个字符串的首地址,如果要改变指针ps所代表的字符串,通常直接改变指针的值,让其指向新的字符串。
  • 字符数组:char str[]="We are students" 用一个字符数组存放一个字符串,如果要改变数组str所代表的字符串只能改变数组元素的内容。

  区别

     字符指针

                        字符数组

存储内容

字符串的首地址

字符串本身(数组的每个元素存放一个字符)

赋值方式

可直接赋值,例:

char *ps;

ps="China";

可在定义时初始化,但不能用赋值语句整体

赋值,要逐字循环赋值

类型

变量,可改变指向

字符数组名是常量,始终代表字符数组首地址

  • 对于字符数组的引用可以逐个字符引用也可以整体引用,输出时%c*ps单个字符输出,%sps一次性输出全部元素,其他数组是不能一次性全部输出的。

5.指针与const

5.1.指针是const

表示一旦得到了某个变量的地址,不能再指向其他变量

int*const q=&i;//q指针是const,q的值即i的地址不能被改变,q指向i的地址的事实不能被改变。

*q= 26; // OK q所指的存储空间的值是可以改变的

q++ // ERROR  q的地址是不能被改变的

判断哪个被const了的标志是const*的前面还是后面,const*前面,*pconst*p所指的存储空间的值不能被修改,const*后面,pconstp所指存储空间地址不能被修改。

  • 指针本身是常量(const影响p)

                int *const p = &a;  // 指针本身是常量

  • p 不能指向别的地址,但是可以修改所指向的值

                *p = 20;  // ✅ 可以修改*p的值

                p = &b;   // ❌ 错误,不能修改p的指向

5.2.所指是const

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
  • const int *p = &i;
  • *p=26; // ERROR!  (*p)const p所指存储空间的值不能变
  • i=26; //OK 
  • p= &j; //OK  p所指存储空间地址能改变
  • 指向常量的指针(const影响int)

                const int *p = &a;  // 指向常量的指针

  • p 可以指向不同的地址,但是不能修改所指向的值

                *p = 20;  // ❌ 错误,不能修改*p的值

                 p = &b;   // ✅ 可以改变指针的指向

5.3.const数组

  • const int a[]={,2,3,4,5,6,};
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是constint所以必须通过初始化进行赋值

5.4.保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
  • int sum(const int a[], int length);

5.5.const int *const p

  • 指向常量的常量指针

                const int *const p = &a;

  • 既不能修改 p 的指向,也不能修改 *p 的值。

6.指针与函数:指针和函数关系的三个方面

6.1.指针作为函数参数

  • 指针可以是普通变量的地址、数组名、字符串首地址等。
  • 指针作为函数参数是将一个变量的地址传送到另一个函数中
  • 指针作为函数参数,将实参传给形参,是地址传递而不再是值传递,实参和形参共享同一段内存,形参的改变会影响实参。
  • 例如:int f(int *p); 在被调用的时候得到某个变量的地址
  • int i=0;f(&i);  在函数里面可以通过这个指针访问外面的这个i
  • 实参用数组名或者指针时程序执行效率差不多,但形参用指针(把数组名改成指针形式就行)有时候可以提高程序执行效率

6.1.1数组变量

  • 无论传递数组首地址的函数参数是数组名还是指针,其实质都是指针,在函数被调用时,该指针通过参数传递指向数组。
  • 注意:函数参数表中的数组,实际上就是指针。对其做sizeof(a)==sizeof(int *a),但是可以用数组的运算符[]进行运算。可以注意到函数调用时,数组名作为实参,形参的数组[]内的数字实际上无效,因为此时的数组实际上就是指针
  • 数组变量是特殊的指针:数组变量本身表达地址,即用数组名,所以:int a[10];int*p=a; 中无需用&取地址但是数组的单元表达的是变量,需要用&取地址·a == &a[0],数组名a实际上是取的数组起始地址&a[0]
  • []运算符可以对数组做,也可以对指针做:p[0]<==> a[0]
  • *运算符可以对指针做,也可以对数组做:*a=25;
  • 数组变量是const的指针,所以不能被赋值:int a[] <==> int * const a=....,是常量指针

6.2.指针型函数

  • 一个函数的返回值可以是各种类型,如int、float、double、char型数据类型,也可以返回一个指针类型数据
  • 格式:类型说明符  *函数名(形参表)
  • 说明:函数名前加一个*表示这是一个指针型函数(函数值是指针),调用它后能得到一个与所指类型说明符相对应的指针(地址)。函数名两侧运算符分别是*与(),因为()比*优先级更高,先与函数名结合说明”函数名()“是一个函数,然后再与*结合,说明此函数的值为指针
  • 调用形式:指针变量=函数名(实参);
  • 示例:
int* getMax(int a, int b) {

    if (a > b) {

        return &a;  // 返回 a 的地址

    } else {

        return &b;  // 返回 b 的地址

    }

}



int x = 5, y = 10;

int *maxPtr = getMax(x, y);

printf("最大值是: %d\n", *maxPtr);  // 解引用指针来获取值

这里,getMax 返回一个指向较大值的指针,maxPtr 保存返回的指针,最后通过 *maxPtr 获取值。

6.3.指向函数的指针(函数指针)

  • C语言中,函数是不能作为参数在函数间进行传递的,但实际应用中可能需要把一个函数作为参数传递给另一个函数,解决的方式是使用指向函数的指针作为参数。这种传递不是传递普通变量的地址,而是传递函数的入口地址。
  • 格式:类型说明符  (*指针变量名)();
  • 说明:类型说明符表示函数返回值的类型,最后的()表示指针变量所指的是一个函数。
  • 示例1:
    double (*fp)();   说明fp是指向函数的指针
    fp=fun;          
    将函数fun在内存中的起始地址赋值给fp,从而使fp指向fun(函数的标识符本身就是地址,可以直接赋值给函数指针)
    
    y=(*fp)(3.5);      调用函数的形式为:(*指针变量名)(实参表)

示例2:

int add(int a, int b) {

    return a + b;

}

int subtract(int a, int b) {

    return a - b;

}



int (*operation)(int, int);  // 声明一个函数指针,指向一个返回 int,接受两个 int 参数的函数



operation = add;  // 将 add 函数的地址赋给函数指针



int result = operation(5, 3);  // 通过函数指针调用 add 函数,结果为 8

printf("结果: %d\n", result);



operation = subtract;  // 将函数指针指向 subtract 函数

result = operation(5, 3);  // 通过函数指针调用 subtract 函数,结果为 2

printf("结果: %d\n", result);

函数指针使得你可以通过动态指定函数来实现多态性或回调机制,比如在事件驱动编程中经常用到。

7.动态内存分配

7.1.malloc

  • #include <stdlib.h>
  • void* malloc(size t size);向malloc申请的空间的大小是以字节为单位的返回的结果是void*,需要类型转换为自己需要的类型
  • (int*)malloc(n*sizeof(int))

7.2.free()

  • 把申请得来的空间还给“系统"
  • 申请过的空间,最终都应该要还
  • 只能还申请来的空间的首地址

7.3.常见问题

  • 申请了没free->长时间运行内存逐渐下降
  • 新手:忘了
  • 老手:找不到合适的free的时机
  • free过了再free
  • 地址变过了,直接去free
  • 记住:写malloc就要写对应的free

 

8.指向指针的指针和指针数组

8.1.指向指针的指针

  • 指针是一种变量,也有地址。可以用另一个指针变量去存储它的地址,该指针就称为指向指针的指针,也叫多级指针
  • 二级指针定义式: 类型名 **指针变量名

8.2.指针数组

  • 指针数组是一个数组,数组中的每一个元素都是指针变量
  • 定义式:类型说明符 *数组名[正整型常量表达式1]…[正整型常量表达式n];
  • 例如:int *p[3],  *q[3][4];    p、q都是指针数组,数组中的每个元素都是指向整型的指针变量。
    •  int *p[3],  q[3][4];
    • p[0]=q[0];p[1]=q[1];p[2]=q[2];,则p[0]、p[1]、p[2]分别指向q数组每行的首地址
  • 指针数组比使用字符数组处理字符串更方便灵活,且节省存储空间
  • 例如:char *ps[3]={"we","are","students"};   指针数组ps的每个元素中存放一个字符串的首地址。

8.3.行指针

  • 定义式:类型说明符  (*指针变量名)[长度];
  • 注意:括号不能少,否则就是指针数组了。
  • 例如:int (*p)[4],  q[3][4]; p=q;  行指针长度要与二维数组的列数相对应
  • 行指针(*p)[4]:
  • (*p)[0]

    (*p)[1]

    (*p)[2]

    (*p)[3]

  • 由于p是指向一维数组的指针变量,因此p+i指向下一个一维数组,等价于a+i,即a[i],是地址。*(p+i)+j是a[i][j]的地址,*(*(p+i)+j)是a[i][j]的值。

9.void* 通用指针类型

9.1.概念

  • 在 C 语言 和 C++ 语言 中,void * 代表通用指针类型(void 指针)它可以指向任何数据类型的地址
  • 由于 void * 没有特定的类型,所以它无法直接解引用(访问数据),但可以转换成其他具体类型的指针再使用

9.2.特点

  • void * 可以指向任何类型的数据,不受数据类型限制。
  • 不能直接解引用:由于 void * 没有具体的类型,不能直接使用 *ptr 访问数据,必须先转换成具体类型的指针。
  • 常用于动态内存分配:malloc() 和 calloc() 返回 void *,需要强制转换成具体类型

9.3.示例

动态分配内存

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

#include <stdio.h>

#include <stdlib.h>

int main() {

    void *ptr = malloc(sizeof(int)); // 分配 4 字节内存

    if (ptr == NULL) {

        printf("内存分配失败\n");

        return -1;

    }

    *(int *)ptr = 10;  // 先转换成 int* 再解引用

    printf(": %d\n", *(int *)ptr);

    free(ptr);  // 释放内存

    return 0;

}

注意:malloc() 返回 void *,所以 ptr 需要转换成 int * 才能使用 *ptr 访问数据。

作为通用参数传递

    1

    2

    3

    4

    5

    6

    7

    8

    9

   10

   11

   12

   13

   14

   15

   16

   17

   18

   19

#include <stdio.h>

void printValue(void *ptr, char type) {

    if (type == 'i') {

        printf("整数: %d\n", *(int *)ptr);

    } else if (type == 'f') {

        printf("浮点数: %.2f\n", *(float *)ptr);

    }

}

int main() {

    int a = 10;

    float b = 3.14;

    printValue(&a, 'i'); // 传递 int 指针

    printValue(&b, 'f'); // 传递 float 指针

    return 0;

}

void * 在 FreeRTOS 任务参数

    1

    2

    3

    4

    5

    6

    7

void vTaskFunction(void *pvParameters) {

    int param = *(int *)pvParameters;  // 转换成 int*

    for (;;) {

        printf("任务参数: %d\n", param);

        vTaskDelay(pdMS_TO_TICKS(1000));

    }

}

注意:pvParameters 允许传递任意类型的数据,但必须在函数内转换成正确的类型才能使用。

9.4.限制

不能直接解引用

    1

    2

void *ptr;

*ptr = 10; // 错误,void * 不能直接解引用

需要先转换成具体类型:

    1

*(int *)ptr = 10; // 先转换成 int*

不能进行指针运算

    1

    2

void *ptr;

ptr++; // 错误,void * 不知道步长

需要转换成具体类型:

    1

    2

int *intPtr = (int *)ptr;

intPtr++; // 现在可以进行指针运算

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值