C语言(四)

本文详细介绍了C语言中的指针,包括指针概念、字符指针与字符串指针的区别、指针数组和数组指针的使用、函数指针的应用、回调函数的概念以及在实际编程中的指针练习。文章通过实例分析了数组名与指针的关系,阐述了传递数组的正确方式,并探讨了二维数组和指针的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

指针进阶

指针概念

字符指针和字符串指针

指针数组

数组指针

数组名是首元素地址;

数组和指针的一些举例区分

传递数组的正确方式

函数指针

函数指针的应用

函数指针数组指针

回调函数

指针练习

指针题

指针联系2

指针笔试题:


指针进阶

指针概念

1,指针就是变量,用来存放地址,地址为一标识一块内存空间
2,指针的大小是固定的4/8个字节(32位平台/64位地址平台)
3,指针式有类型的,指针的类型决定了指针的+—整形的步长,指针解引用时候操作的权限
4,指针的运算

字符指针和字符串指针

字符指针

char ch = 'c';

char *pc = &ch;

字符串指针

    char* pc ="hello world";

可以把字符串看作一个含有‘\0’字符的字符数组,那么其实字符串指针是一种特殊的数组指针。

来看一个例子

    char str1[] = "hello";
    char str2[] = "hello"; 
    char* str3 = "hello";
    char* str4 = "hello";  

判断str1和str2,str3和str4分别是否相等。

int main()
{
	char str1[] = "hello";
	char str2[] = "hello"; 
	char* str3 = "hello";
	char* str4 = "hello";
	if (str1 == str2)
		printf("str1==str2");
	else
		printf("str1!==str2");
	if (str3 == str4)
		printf("str3==str4");		
	else
		printf("str3!==str4");
	return 0;
}

输出:结果来看,str1和str2不相等,str3和str4相等

str1!==str2
str3==str4

原因:str1和str2是分别定义了一个数组,给他们赋值为”hello",所以不想等。

str3和str4是定义指针,指向同一个常量字符串,这个字符串储存在内存中,有自己的地址,指针都指向这个地址,str3和str4自然是相等的.

注意:常量字符串对应的指针不能被修改
char* arr = "hello";     这种常量字符串不能被修改而且地址固定

指针数组

存放指针的数组


int* arr1[10];         整形指针数组
char* arr2[4];        一级字符指针的数组
char** arr3[5];       二级字符指针的数组

数组指针

数组指针  --  一个指针,指向数组的指针

比如:

整形指针--是一种指向整形的指针
字符指针--是一种指向字符的指针
 

定义数组指针的方式

int arr[10] = { 0 };

int (*p)[10] = &arr;  

如果不加括号,p会先和【 】结合,会被编译器认为是指针数组。

所以要加括号,保证定义的是指针。

举例:

    double* d[5];

    double * (*pd) [ 5 ] = &d ;        double*--表示指针指向的数组的元素类型是double*;
    (*pd)---表示这是一个指针
    [5]---表示这是一个数组指针

        pd表示一个指针数组指针,这是一个指针,指向一个含有五个元素的数组,数组元素类型为double指针。

数组名是首元素地址;


但是有两个意外:
1,sizeof(数组名) --  数组名表示整个数组,计算的是整个数组的大小,单位是字节
2,&数组名 -- 数组名表示整个数组,取的是整个数组的地址

数组和指针的一些举例区分

int arr[5];         整形数组
int* arr[5];        指针数组
int(*arr)[5];       数组指针


int(*parr[10])[5];            数组指针数组   
parr是一个存储数组指针的数组
该数组能够存放10个数组指针
每个数组指针指向一个数组,数组5个元素,每个元素是int类型

传递数组的正确方式

一维数组传参
void test(int arr[]) {}
void test(int arr[10]) {}
void test(int *arr) {}


 一维(指针)数组传参
void test2(int *arr2[20]) {}
void test2(int **arr2) {}

二维数组传参
void test(int arr[3][5]) {}
void test(int arr[][5]) {}
void test(int (*arr)[5]) {}

二级指针可以传的参数:1,二级指针;2,一级指针的地址;3,一级指针数组名(一级指针数组的首元素地址)

指针
一级指针:
int *p        整形指针
char *ch        字符指针
void *vp        无具体类型指针

二级指针:
char **p;        
int **p;

数组指针:指向数组的指针
int(*p)[4];

数组
一维数组;
二维数组
指针数组;

函数指针

函数指针
指向函数的指针:存放函数地址的指针

直接上结论:函数名就是函数地址,就是函数指针

数组名 != &数组名
函数名 == &函数名

举例:

int add(int a, int b)
{
    return a + b;
}
int main()
{
    printf("%p\n", &add);
    printf("%p\n", add);
    int (*pf)(int,int) = &add;        //pf是一个函数值指针变量
    //第一个int表示返回类型; (*pf)表示指针  ;(int ,int)表示参数
    //这样一个函数指针就创建完成了
    return 0;
}
//输出
005B1023
005B1023

很明显:函数名就是函数地址;

根据上面推理:

pf == &add 而且  &add == add;   *pf == add; 

可推:pf == *pf;

所以,在函数指针中,*pf与pf是完全相等的,*在函数指针这里可带可不带,带上只是为了便于理解。

即:pf(1,2)与add(1,2)一样。

经过编译器验证,甚至在声明函数指针的时候,也可以不带 * 。

结论:函数指针中的*是个摆设;函数名就是它的指针:存放的是函数地址

示例:

        1, 声明一个指向含有10个元素的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int*,
          int   (*  (*p)[10]  )    (int*)

        2,上一段非常经典的代码

        分析代码的作用:      ((void ( * ) ()   0  )  (); 
第一步:  ( void (*)()  )这是一个函数指针类型
            ( void (*p)()  )表示p这个函数指针;去掉p表示函数指针类型;

第二步:把指针类型加括号,并在后面加上0;表明这是一个强制类型转换;把0处的地址转为函数指针类型。
第三步: 加上*表示调用0地址处的函数,()表示无参擦传递;
           该函数无参,返回类型为void;

需要注意的是,因为函数指针加不加 * 都可以,所以,第三步的 * 去掉也是正确的。

        3,分析这段代码:    void (*signal  ( int, void ( * )(i nt ) ) ) (int);

    void(*)(int)函数指针类型作为变量
    signal(   int,  void(*)(int)  )是一个函数指针变量,int 和 void(*)(int)是它的参数
    void(*   )(int) 也是一个函数指针类型,返回类型是函数指针类型
1,sinal和()先结合,说明signal是函数名
2,signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
 该函数指针指向一个参数为int,返回类型为void的函数
3,signal函数的返回类型是一个函数指针
该函数指针指向一个参数为int,返回类型为void的函数
signal是一个函数声明

简化:


通过   typedef - 对类型进行重定义
typedef void(*pfun_t)(int);        //对void(*)(int)的函数指针类型重命名为pfun_t;
typedef unsigned int uint;        //对unsigned int 重命名为uint
pfun_t signal(int, pfun_t);        //与原来的式子是等价的

函数指针的应用

int add(int x, int y)
{
    return x+y;
}
int sub(int x, int y)
{
    return x - y;
}

int main()
{
    int (*pf1)(int, int)  = add;
    int (*pf2)(int, int)  = sub;
    int (*pfarr[2])(int, int) = {pf1,sub};//函数指针数组
    //函数指针数组可以存放同类型的函数指针
    printf("1+2 = %2d\n", pfarr[0](1, 2));
	printf("1-2 = %2d\n", pfarr[1](1, 2));
    return 0;
}
//输出
1+2 =  3
1-2 = -1

 函数指针数组(也叫转移表)可以存放多个同类型函数,这样在调用函数的时候,就会显得更加方便;

函数指针数组指针

先复习一下:

整形数组
int arr[5];
整形数组的指针
int(*p)[5] = &arr;

指针数组(整形)
int* arr[5];
整形指针数组的指针
int* (*p2)[5] = &arr;
p2是指向【整形指针数组】的指针

函数指针数组指针:

int(*p)(int, int);    //函数指针
int(*p[5])(int, int);    //函数指针数组;
p3 = &p;    //取出的是函数指针数组的地址
p3就是一个指向【函数指针数组】的指针
int (* (*p3) [5])(int, int);    //函数指针数组的指针

回调函数

回调函数 - 通过函数指针调用的函数


如果你把一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。


回调函数不是由该函数的实现方式直接调用,而是在特定的事件或者条件发生时由另外的一方调用的,用于对该事件或条件进行响应
这种机制称为回调函数机制


注意跟函数递归地区别:函数递归是自己调用自己,回调是调用其他。

比如:

int add(int x, int y)
{
    return x+y;
}
int sub(int x, int y)
{
    return x - y;
}
int cal(int (*pf)(int,int))
{
    return pf(1,2);
}

int main()
{
    int (*pf1)(int, int)  = add;
    int (*pf2)(int, int)  = sub;
    printf("1+2 = %2d\n", cal(add));
	printf("1-2 = %2d\n", cla(pf2));
    return 0;
}
//输出
1+2 =  3
1-2 = -1

指针练习


sizeof(数组名) - 计算的是整个数组的大小
&数组名 - 数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素地址
int main()
{
    int a[] = { 1,2,3,4 };
    printf("%d\n", sizeof(a));        //16 - 计算的是整个数组的大小
    printf("%d\n", sizeof(a + 0));    //8 - (a+0)表示第一个元素的地址 - 表示地址的大小(4或者8)(32位或64位)
    printf("%d\n", sizeof(*a));        //4 - a表示首元素地址 - 4个字节
    printf("%d\n", sizeof(a + 1));    //8 - 表示第二个元素地址
    printf("%d\n", sizeof(a[1]));    //4 - 表示第二个元素
    printf("\n");

    printf("%d\n", sizeof(&a));        //8 - &a是数组地址,计算的是地址大小
    printf("%d\n", sizeof(*&a));    //16 - 计算的是一个数组的大小
    printf("%d\n", sizeof(&a + 1));    //8 - 表示跳过整个数组之后的空间的起始地址
    printf("%d\n", sizeof(&a[0]));    //8 - 表示第1个元素地址
    printf("%d\n", sizeof(&a[0] + 1));    //8 - 表示第二个元素地址

    printf("\n");
    //字符数组
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(arr));        //6 - 数组大小
    printf("%d\n", sizeof(arr + 0));        //8 - 数组首元素地址
    printf("%d\n", sizeof(*arr));        //1 - 解引用 - 数组首元素
    printf("%d\n", sizeof(arr[1]));        //1 - 数组第二个元素
    printf("%d\n", sizeof(&arr));        //8 - 整个数组地址
    printf("%d\n", sizeof(&arr + 1));        //8 - 跳过整个数组地址后的空间地址
    printf("%d\n", sizeof(&arr[0] + 1));    //8 - 第二个元素地址(首元素地址+1)

    printf("\n");
    //strlen()函数来计算
    //strlen()函数接收的是地址,不能是数字,然后从地址的位置往后查一直算到"\0";
    printf("%d\n", strlen(arr));        //随机值 - 字符串大小
    printf("%d\n", strlen(arr + 0));        //随机值 - 数组首元素地址
    //printf("%d\n", strlen(*arr));        //报错 - 解引用 - 数组首元素
    //printf("%d\n", strlen(arr[1]));        //报错 - 数组第二个元素
    printf("%d\n", strlen(&arr));        //随机值 - 整个数组地址
    printf("%d\n", strlen(&arr + 1));        //随机值-6  - 跳过整个数组地址后的空间地址
    printf("%d\n", strlen(&arr[0] + 1));    //随机值-1  - 第二个元素地址

    printf("\n");
    //字符串 - sizeof()
    char arr1[] = "abcdef";
    printf("%d\n", sizeof(arr1));        //7 - 数组大小
    printf("%d\n", sizeof(arr1 + 0));        //8 - 数组首元素地址
    printf("%d\n", sizeof(*arr1));        //1 - 解引用 - 数组首元素
    printf("%d\n", sizeof(arr1[1]));        //1 - 数组第二个元素
    printf("%d\n", sizeof(&arr1));        //8 - 整个数组地址
    printf("%d\n", sizeof(&arr1 + 1));        //8 - 跳过整个数组地址后的空间地址
    printf("%d\n", sizeof(&arr1[0] + 1));    //8 - 第二个元素地址(首元素地址+1)

    printf("\n");
    printf("%d\n", strlen(arr1));        //6 - 字符串大小
    printf("%d\n", strlen(arr1 + 0));        //6 - 字符串大小
    //printf("%d\n", strlen(*arr1));        //报错 - 解引用 - 数组首元素
    //printf("%d\n", strlen(arr1[1]));        //报错 - 数组第二个元素
    printf("%d\n", strlen(&arr1));        //6 - 整个数组地址
    printf("%d\n", strlen(&arr1 + 1));        //随机值 - 跳过整个数组地址后的空间地址
    printf("%d\n", strlen(&arr1[0] + 1));    //5 - 第二个元素地址(首元素地址+1)

    printf("\n");
    //字符串指针
    char* p = "abcdef";        //字符串数组
    printf("%d\n", sizeof(p));        //8 - 
    printf("%d\n", sizeof(p + 1));        //7 - 
    printf("%d\n", sizeof(*p));        //1 - 解引用 - 表示第一个字符元素
    printf("%d\n", sizeof(p[1]));        //1 - 表示第二个字符元素
    printf("%d\n", sizeof(&p));        //8 - 
    printf("%d\n", sizeof(&p + 1));        //8 - 
    printf("%d\n", sizeof(&p[0] + 1));    //8 - 

    printf("\n");
    printf("%d\n", strlen(p));        //6 - 字符串数组的大小(p表示地址)
    printf("%d\n", strlen(p + 1));        //5 - 数组首元素地址(p+1表示地址,后移了一个字节)
    //printf("%d\n", strlen(*p));        //报错 - 解引用 - 数组首元素
    //printf("%d\n", strlen(p[1]));        //报错 - 数组第二个元素
    printf("%d\n", strlen(&p));        //随机值 - 指针的地址 
    printf("%d\n", strlen(&p + 1));        //随机值 - 指针地址的下一个地址
    printf("%d\n", strlen(&p[0] + 1));    //5 - 第二个字符元素地址(首元素地址+1)


    //char ch = 'q';
    char* pc = "hello world";
    //本质上是把字符串的首字符地址存储到了pc中

    printf("%c\n", *pc);
    printf("%p\n", pc);
    printf("%c\n", pc[0]);
    printf("%c\n", pc[10]);
    printf("%s\n", *pc);        //这一句有问题;


    return 0;
}

指针题

指针联系2


二维数组

int main()
{
    int a[3][4] = { 0 };

    printf("%d\n", sizeof(a));        //48 - 表示整个数组
    printf("%d\n", sizeof(a[0][0]));        //4 - 第一行第一列的元素
    printf("%d\n", sizeof(a[0]));        //16 - 当访问某一行的时候,相当于一个一维数组,是该行数组的大小
    printf("%d\n", sizeof(a[0] + 1));        //8 - a[0]并没有单独放在内部,也没有取地址,所以a[0]表示第一行第一个元素的地址
    printf("%d\n", sizeof(*a[0] + 1));        //4 - 第一行第二个元素
    printf("%d\n", sizeof(a + 1));        // 8 - 第二行的地址
    printf("%d\n", sizeof(*(a + 1)));        //16 - (a+1)表示第二行的地址,*(a+1)表示第二行的数据;表示数组的第二行            // *(a+1) == a[1]
    printf("%d\n", sizeof(&a[0] + 1));        //8 - 表示地址(a[0]是第一行的数组名;&a[0]表示第一行的地址;+1表示第二行的地址)
    printf("%d\n", sizeof(*(&a[0] + 1)));        //16 - 表示第二个一维数组;(表示第二行数据)
    printf("%d\n", sizeof(*a));        //16 - 表示元素 (第一个一维数组)(第一行的数据)
    printf("%d\n", sizeof(a[3]));        //16 - 不需要访问这个a[3];

    //总结一个规律:当访问二维数组的某一行的时候,数组把行看作一个元素
    //这时候把二维数组看作一维数组,那么它的元素就是一个个以为数组a[0] -- a[2];
    //sizeof(a[0]),就表示该一维数组的大小
    return 0;
}


数组本质上是一个指针,数组指针和数组名打印的地址是一样的
  区别: 
数组名一般来讲表示,数组首元素地址;    

有两个例外:

1,sizeof(arr)这里计算的是整个数组的大小;    2,&arr表示的是整个数组的地址


数组指针指的就是整个数组

总结:
1,sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
2,&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
3,除此之外所有的数组名都表示首元素地址

指针笔试题:


//第一题,

int main()
{
    int a[5] = { 1,2,3,4,5 };
    int* p = (int*)(&a + 1);            //p表示跳过数组a之后的地址,并进行指针类型转换为int*类型
    printf("%d,%d\n", *(a + 1), *(p - 1));//p-1 表示地址往前数4个字节;再解引用,就是a[4];
    printf("%d,%d\n", *(a + 1), *(p));
    return 0;
}


//输出:2,5


//第二题,考察的是:指针类型决定了你的运算

struct test {
    int num;
    char* pcname;
    short cha[2];
}*p;
int main()
{
    printf("%d\n", sizeof(*p));
    printf("%p\n", p);
    printf("%p\n", p + 0x1);                //指针+1跳过了24个字节(结构体类型)的大小
    printf("%p\n", (unsigned long)p + 0x1);    //转换为一个长整形数字,不再是指针了,数字+1就是+1,与指针不一样
    printf("%p\n", (unsigned long*)p + 0x1);
    printf("%p\n", (long long*)p + 0x1);
    printf("%p\n", (unsigned int*)p + 0x1);    //指针+1跳过了一个int类型的字节数(4)个
    return 0;
}


//输出:24
//0000000000000000
//0000000000000018
//0000000000000001
//0000000000000004
//0000000000000004


//第三题,

int main()
{
    int a[4] = { 1,2,3,4 };
    int* p = (int*)(&a + 1);
    int * p1 = (int*)((int)a + 1);
    printf("%x\n", p[-1]);
    printf("%x\n", *p1);
    printf("%x,%x\n", p[-1], *p1);
    return 0;
}


//输出:4,2000000(0x0200_0000)


//第四题
int main()
{
    int a[3][2] = { (0,1),(2,3),(4,5) };
    //()表示逗号表达式
    //数组实际上为{1,3,5)
    int* p;
    p = a[0];    //数组首元素地址
    printf("%d", p[0]);
    return 0;
}
//输出:1


//第五题,
int main()
{
    int a[5][5] = { 0 };
    int i = 0;
    int j = 0;
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
            a[i][j] = 5 * i + j;
        }
    }
    int(*p)[4];                               本质上,二维数组就是以恶以及数组指针。
    p = &a;
    //p = &a;
    printf("%p\n", *(p + 1));
    printf("%p\n", p + 1);
    printf("%p\n", p[1]);
    printf("%d\n", *(*(p + 1)));
    printf("%d\n", *(p[1]));
    printf("%d\n", a[0][0]);    
    printf("%d\n", a[4][4]);    //4*5+4 = 24
    printf("%d\n", p[1][1]);    //1*4+1 = 5
    printf("%d\n", p[4][2]);    //4*4+2 = 18
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    printf("%p,%d\n", &a[4][2] - &p[4][2], &a[4][2] - &p[4][2]);
    return 0;
}
//输出:fffffffffffffffc,-4
//        0000000000000004, 4
//因为数组指针+1表示跳过一整个数组
//第六题,
int main()
{
    int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
    int a0[2][2][2] = {1,2,3,4,5,6,7,8};
    int a1[] = {1,2,3,4,5,6,7,8,9,10};
    int* p1 = (int*)(&a + 1);
    int* p2 = (int*)(*(a + 1));
    printf("%d\n", ***a0);
    printf("%d\n", **a);
    printf("%d\n", *a1);    //可以这样理解:数组就是指针;一维数组就是一级指针;二维数组就是二级指针,需要解引用两次才能获得数据
    printf("%p\n", a[1]);
    printf("%p\n", p2);
    printf("%d\n", *a[1]);
    printf("%d,%d", *(p1 - 1), *(p2 - 1));
    return 0;
}
//10,5
//第七题。
int main()
{
    char* a[] = { "at", "alibaba","work","zbcd"};    //指针数组:存储了这四个字符串的地址
    char** pa = a;                            //二级指针指向指针数组的首元素地址;首元素是一个指向work的指针a[0]
    pa++;                                    //二级指针+1跳过一个一级指针
    pa++;                                    
    printf("%p\n", a);
    printf("%p\n", a[0]);
    printf("%p\n", a[1]);
    printf("%p\n", a[2]);
    printf("%d\n", (char*)(a[1])- (char*)(a[0]));
    printf("%s\n", *pa);
    printf("%c\n", **pa);
    return 0;
}
//输出:at
//因为字符串数组很特殊;可以直接写:char*p = "abc";
//第八题,
int main()
{
    char* c[] = { "enter","new","point","first" };
    //指针数组,内含4个指针,指向4个字符串
    char** cp[] = { c + 3,c + 2,c + 1,c };
    //c表示数组首元素地址,也就是第一个指针的地址,实际上是一个二级指针
    //cp是一个二级指针数组,内部表示四个二级指针,内部是逆序指向c的4个指针的
    char*** cpp = cp;
    //cpp表示指针数组,可以看作一个三级指针,指向二级指针数组cp[0]
    printf("%s\n", *c+1);
    printf("%s\n", **++cpp);
    //先++再引用;++cpp表示跳过这个二级指针数组即为指向cp【1】== c[2];解引用两次得到字符串数组;
    //输出:point
    printf("%s\n", *-- * ++cpp + 3);
    //*--(*++cpp) +3
    //++cpp ==> cp +2 ==>解引用后:就是c+1; ==> 这是对cp的元素进行操作--之后就是c表示cp【2】 ==c(数组c首元素地址:表示第一个指针的地址);==> 再解引用就是获得字符串数组“enter”的指针;
    // ;因为这个指针是char*类型的;当+3的时候会跳过3个char指针;==>所以这个指针会从er开始打印;结果为er
    //输出:er
    printf("%s\n", *cpp[-2] + 3);
    //cp+2-2 ==》cp ,把cp解引用表示c+3;再解引用表示指向“first”的指针;再+3表示;跳过3个char*类型的指针,所以,结果为st
    //输出:st
    printf("%s\n", cpp[-1][-1] + 1);
    //*(*(cpp-1)-1) +1
    //cp+2-1 == cp+1==> 解引用之后为c+2 ;在-1之后表示c+1==> 解引用表示指向“new”的指针;==> +1表示“ew”
    //输出:ew
    return 0;
}

//小知识:
//数组指针相当于一个二级指针
//字符串虽然是一个数组,但是字符串指针也是一级指针,不能算二级指针
//字符串指针数组是二级指针
//普通数组指针需要解引用两次;字符串指针只需要解引用一次

int main()
{
    char* p = "hello word";
    int a[4] = { 1,2,3,4 };
    int(*arr)[4] = a;
    printf("%c\n", *p);
    printf("%s\n", p);
    printf("%d\n", **arr);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值