指针的概念

  • 系统给虚拟内存的每个存储单元分配了一个编号,从0x00000000 ~ 0xffffffff ,这个编号称之为地址

  • 指针变量:用来存放一个地址的变量

  • 作用 :可以通过指针来间接访问内存

  • 每定义一个变量,程序会向计算机申请一个内存空间,空间的大小取决于数据类型

    pic

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>

int main(){

printf("int类型指针占用内存:%d\n",sizeof(int *));
printf("float类型指针占用内存:%d\n",sizeof(float *));
printf("double类型指针占用内存:%d\n",sizeof(double *));
printf("char类型指针占用内存:%d\n",sizeof(char *));

return 0;
}

运行结果

64位

int类型指针占用内存:8
float类型指针占用内存:8
double类型指针占用内存:8
char类型指针占用内存:8

32位

int类型指针占用内存:4
float类型指针占用内存:4
double类型指针占用内存:4
char类型指针占用内存:4

扩展在32位操作系统下,指针占用4byte内存,在64位操作系统下,指针占用8byte内存

指针变量的定义

  • 语句:数据类型 * 变量名

  • eg.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include<stdio.h>

    int main(){

    // 定义一个指针
    int num1 = 10;
    // 指针的定义语法: 数据类型 *变量名
    // 指针的数据类型需要和变量的数据类型相同
    int *p;
    // &取地址符
    p = &num1;
    printf("num1的地址是:%p\n",&num1);
    printf("指针p是:%p\n",p);

    return 0;
    }
  • 运行结果(不同设备运行结果不同,但两个地址结果一定相同)

    num1的地址是:000000000061FE14
    指针p是:000000000061FE14

    请按任意键继续. . .

指针的解引用

  • 语法:*指针名称

  • 作用:通过指针找到对应的内存地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include<stdio.h>

    int main(){

    // 定义一个指针
    int num1 = 10;
    // 指针的定义语法: 数据类型 *变量名
    // 指针的数据类型需要和变量的数据类型相同
    int *p;
    // &取地址符
    p = &num1;

    // 使用指针
    // 可以通过解引用的方式来找到指针指向的内存
    // 指针前面加一个*表示解引用
    printf("指针p指向的内存的值为:%d\n",*p);
    *p = 100; // 可以通过修改指针p指向内存的值来修改变量的值
    printf("现在num1的值:%d\n",num1);
    printf("现在指针p指向的内存的值是:%d\n",*p);

    return 0;
    }

运行结果

指针p指向的内存的值为:10
现在num1的值:100
现在指针p指向的内存的值是:100

请按任意键继续. . .

指针与数组元素

  • 我们定义的数组,就是多个相同类型变量的集合,,每个元素都有对应的内存空间,都有对应的地址,因此,指针也可以存放数组元素的地址。

  • eg.

    1
    2
    3
    4
    5
    6
    7
    8
    #include<stdio.h>

    int main(){
    int a[5];
    // 定义指针变量p,保存a[0]的地址
    int *p = &a[0];
    return 0;
    }

pic1

  • 应用(数组元素的引用)

    1. 数组名[下标]

      1
      2
      int a[5];
      a[2] = 100;
    2. 指针加下标

      • c语言规定:数组的名字就是数组的首地址,即第0个元素的地址,就是&a[0] 。
      1
      2
      3
      4
      int a[5];
      int *p;
      p = a;
      p[2] = 100; // 等同于a[2] = 100;

      ! ! ! p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值

      1
      2
      p = &a[0]; // 正确
      a = &a[0]; // 错误
    3. 通过指针变量运算加取值的方法来引用数组的元素

      • p是数组的第0个元素,相当于a[0],那么p + 2就相当于a[2]
      1
      2
      3
      4
      int a[5];
      int *p;
      p = a;
      *(p + 2) = 100; // 等同于a[2] = 100;
    4. 通过数组名+取值的方法引用数组的元素

      • 由于C语言中数组的名字也是一个指针,因此我们可以通过如下方式引用数组中的元素
      1
      2
      int a[5];
      *(a+2)=100; // 等同于a[2]=100;

指针的运算

  1. 指针加整数,往下指变量(仅指针指向数组时有意义)

    1
    2
    3
    4
    int a[5];
    int *p;
    p=a;
    p+2; // p是&a[0],p+2是&a[2]
  2. 两个相同类型指针比较大小(仅两个相同类型指针变量指向同一数组时有意义)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include<stdio.h>;

    int main(){
    int a[10];
    int *p,*q;
    p = &a[0];
    q = &a[6];
    printf("%d\n%d\n",p,q);
    /* 6421984
    6422008
    请按任意键继续. . .
    两个结果前面的指针小于后面的指针,由于整型为4byte,因此两者间相差24 */
    printf("%d\n",q - p); // 6 即两个指针指向的之间有多少个元素

    return 0;
    }
  3. 两个相同类型的指针做减法(仅两个相同类型指针变量指向同一数组时有意义)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include<stdio.h>;

    int main(){
    int a[10];
    int *p,*q;
    p = &a[0];
    q = &a[6];
    printf("%d\n",q - p); // 6 即两个指针指向的之间有多少个元素
    return 0;
    }
  4. 两个相同类型的指针可以相互赋值(void*类型除外)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include<stdio.h>;

    int main(){
    int a[10];
    int *p,*q;
    p = &a[0];
    q = &a[6];
    // 两个相同类型的指针可以互相复制,例如下面的例子中,q保存了p的地址也指向a
    p = a;
    q = p;
    // 编译运行的结果是p和q指向的同一个地址
    printf("%p\n%p\n",p,q);
    return 0;
    }

指针数组

  • 概念:指针数组是若干个相同类型的指针变量构成的集合(数组)

  • 语法:数据类型 * 数组名 [元素个数];

  • eg.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include<stdio.h>

    int main(){
    char *chr[6] = {"China","America","Russia","French","Btitain","Welcome"};
    for(int i = 0;i < 6;i++){
    printf("%s\n",chr[i]);
    }
    return 0;
    }

    运行结果

    China

    America

    Russia

    French

    Britain

    Welcome

Snipaste_2025-08-22_21-15-26

指针的指针

  • 概念:指针变量的地址

  • eg.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include<stdio.h>

    int main(){

    // 指针的指针
    int a = 0;
    int *p = &a;
    int **q = &p; // q保存了p地址

    return 0;
    }

    理论上,我们可以创建指针的指针的指针乃至更多,无限套娃

指针与字符串

  • 字符串的概念:字符串就是以’\0’结尾的若干的字符的集合(数组)
  • 字符串的地址:即字符串中第一个字符的地址,例如字符串“helloworld”中字符串的地址即为字符“h”的地址
1
char *s = "helloworld";

数组指针

  • 概念:指向数组的地址

  • 语法:指向数组的数据类型(*指针变量名)[指向数组的元素个数]

  • eg.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include<stdio.h>

    int main(){

    int b[2][3] = {1,2,3,4,5,6};
    // 这里二维数组需要两次解引用才能的到b[0][0] 具体原因见下方补充说明
    printf("b = %d\n",**b);
    printf("b + 1 = %d\n",b[0][0]);

    int (*t)[3];
    printf("b = %p\n",b);
    printf("b + 1 = %p\n",b + 1);
    t = b;
    printf("t = %p\n",t);
    printf("t + 1 = %p\n",t + 1);

    return 0;
    }

    运行结果

    b = 1
    b + 1 = 1
    b = 000000000061FDD0
    b + 1 = 000000000061FDDC
    t = 000000000061FDD0
    t + 1 = 000000000061FDDC

    补充说明

    这里二维数组b的数组名实际上指向的是该二维数组中的第一个一维数组的地址,b[2] [3]可以具象化为一个2*3的表格,*b得到的是第0行的地址,**b得到的就是第0行第0列的元素的地址了

    Snipaste_2025-08-23_10-09-44

  • 各种数组指针的定义

    1. 一维数组指针,加1即为下一个一维数组

      如 int (*p)[5]; ,配合每行有5个int型元素的二维数组如 int a[3] [5] 、int b[4] [5]、 int c[5] [5]、int d[6] [5]

    2. 二维数组指针,加1即为下一个二维数组

      如 int (*p)[4] [5]; 配合三维数组来用。三维数组中由若干个4行5列二维数组构成,如int a[3] [4] [5];、 int b[4] [4] [5];、int c[5] [4] [5];、 int d[6] [4] [5]; 这些三维数组,有个共同的特点,都是有若干个4行5的二维数组构成。

    eg.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include<stdio.h>


    int main()
    {
    int a[3][4][5];
    printf("a=%p\n", a);
    printf("a+1=%p\n", a + 1);// a 和 a+1 地址编号相差 80 个字节
    // 验证了a+1 跳一个4行5列的一个二维数组

    int(*p)[4][5];
    p = a;
    printf("p=%p\n", p);
    printf("p+1=%p\n", p + 1);//p 和 p+1 地址编号相差也 80 个字节
    return 0;
    }

数组名字与指针变量的区别

  • 相同点:a为数组的名字,是a[0]的地址,p = a则p保存了a[0]的地址,两者都指向a[0],所以在引用数组时两者等价

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include<stdio.h>

    int main()
    {
    int a[3] = {0,1,2};
    int* p;
    p = a;
    printf("a[2] = %d\n", a[2]); // 2
    printf("*(a + 2) = % d\n", *(a + 2)); // 2
    printf("p[2] = %d\n", p[2]); // 2
    printf("*(p + 2) = % d\n", *(p + 2)); // 2
    return 0;
    }
  • 不同点:a作为数组的名字,它是一个常量,值永远是a[0]的地址;而p则为指针变量,可以为其赋值。对a取地址相当于数组指针,而对p取地址则为指针的指针。

指针与函数

  1. 指针传参

    • eg.
    1
    2
    int num;
    scanf("%d",&num);
    • 可以将变量的地址传入函数,在被调函数中来修改主调函数中的变量(局部变量)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<stdio.h>

    void swap(int *p,int *q){
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;
    }

    int main(){
    int a = 10,b = 20;
    swap(&a,&b);
    printf("a = %d,b = %d",a,b); // a = 20,b = 10
    return 0;
    }
  2. 指针传数组

    • 传一维数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include<stdio.h>

    void printArray(int *p){
    printf("%d\n",p[2]); // 3
    printf("%d\n",*(p + 3)); // 4
    }

    int main(){
    int a[5] = {1,2,3,4,5};
    printArray(a);
    return 0;
    }
    • 传二维数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<stdio.h>

    // int(*p)[3] 将二维数组的三个一维数组依次传入
    void printArray(int(*p)[3]){
    // 通过数组调用
    printf("%d\n",p[1][1]); // 5
    // 通过指针调用
    printf("%d\n",*(*(p + 1) + 2)); // 6
    }

    int main(){
    int a[2][3] = {1,2,3,4,5,6};
    printArray(a);
    return 0;
    }
    • 传指针数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include<stdio.h>

    void printChar(char **p){
    for(int i = 0;i < 3;i++){
    printf("%s\n",p[i]);
    }
    }

    int main(){
    char *p[3] = {"Hello","World","Programe"};
    printChar(p);
    return 0;
    }
  3. 指针作返回值

    • 一个函数可以返回整型数据、字符数据、浮点型的数据,也可以返回一个指针。

    • 这里返回的是fun函数里面局部变量str的地址,函数运行结束,内存就被释放了,返回这个地址,也没有意义了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<stdio.h>

    char* fun() {
    char str[100] = "hello world";
    return str;
    }


    int main()
    {
    char* p;
    p = fun();
    printf("%s\n", p); // 乱码
    return 0;
    }
    • 在函数里面给局部变量加static修饰,因为静态数组的内容,在函数结束后,仍然存在
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<stdio.h>

    char* fun() {
    static char str[100] = "hello world";
    return str;
    }


    int main()
    {
    char* p;
    p = fun();
    printf("%s\n", p); // hello world
    return 0;
    }
  4. 函数指针

    • 概念:定义一个指针变量,来存放函数的地址
    • 语法:返回值类型(*函数指针变量名)(形参列表);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<stdio.h>

    int add(int a,int b){
    return a + b;
    }

    int main(){
    int (*p)(int,int);
    p = add;
    // 通过函数名调用
    printf("%d\n",add(1,2));
    // 通过函数指针调用
    printf("%d\n",(*p)(1,2));
    return 0;
    }
  5. 函数指针数组

    • 由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储。函数指针数组是个数组,它的每个元素都是一个函数指针变量
    • 语法:类型名(*数组名[元素个数])(形参列表)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include<stdio.h>

    int max(int a,int b){
    if(a > b){
    return a;
    }else{
    return b;
    }
    }

    int min(int a,int b){
    if(a < b){
    return a;
    }else{
    return b;
    }
    }

    int add(int a,int b){
    return a + b;
    }

    int main(){
    int (*p[3])(int,int) = {max,min,add};
    printf("%d\n",(*p[2])(4,8)); // 12
    return 0;
    }

特殊指针

  1. 空类型指针void *

    • void * 并不是指向void类型的变量,而是一种通用指针,任何数据类型的地址都可以为其赋值
    1
    2
    3
    int *p;
    void *q;
    q=p // 是可以的,不用强制类型转换
  2. 空指针NULL

    • p哪里都不指向,用于指针的初始化
    1
    int *p = NULL;

main 函数传参

  • int argc:argc 表示命令行参数个数(argument count),包括程序本身,即 argc 最小为1
  • char *argv[]:使用argument value来记忆,参数值的意思,argv[] 是一个指向字符串数组的指针,其中每个元素是一个指向传递给程序的参数的指针(argument vector),这些字符串是命令行参数。
1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>

int main(int argc,char *argv[])
{
int i;
printf("argc=%d\n", argc);
for ( i = 0; i < argc; i++)
{
printf("argv[%d]=%s\n", i,argv[i]);
}
return 0;
}

无参数运行结果

argc=1
argv[0]=test.exe

1
test.exe abc 123

有参数运行结果

argc=3
argv[0]=test.exe
argv[1]=abc
argv[2]=123