C语言指针详细梳理

目录

一、指针变量

1.概念

2.指针类型

3.为什么需要用到指针

举例1

举例2

举例3

二、指针和数组

1.概念

2.指针增量和数组的关系

3.函数指针数组结合使用

4.二维数组

5.数组指针

6.指针数组

6.1定义

 6.2举例

7.二级指针

8.二级指针和二维数组

三、指针和函数

1.函数指针

1.1 定义:

1.2 举例

1.3函数指针作为入口参数

1.4函数指针作为返回值

四、字符串部分

1.定义

2.字符串的存储方式

3.sizeof 、 strlen、memset、mencpy 的使用

3.1sizeof和strlen

3.2memset 和 memcpy

4.其他常用字符串操作函数

4.1puts()和 gets();    

4.2strcpy 拷贝字符串数组

4.3strcat 函数用于连接两个字符串

4.4strncmp 函数用于比较两个字符串的前 n 个字符

4.5strchr

4.6strstr

五、结构体指针


写这篇文章主要是用来总结下指针的内容,加深自己的印象,能力有限讲解的不是很清楚

一、指针变量

1.概念

指针变量 == 存放地址的变量

int a = 10;     //定义一个变量
int *p;         //定义一个指针变量,*是一个标识符告诉系统这是一个指针变量,用来存放别人的地址
                // * 标识作用,只产生在指针变量声明的时候,其他地方都是运算作用
p = &a;         //把a的地址赋给指针变量
printf("%d\n",a);
printf("%d\n",*p);//这里的*是取地址的意思, *p 相当于 *(&a) 相当于 a,通过a的地址来访问a

2.指针类型

定义指针时候要求类型一致

下面程序定义了一个 int变量,分别用 int 型指针和 char 型指针指向它,我们可以看到他们输出的地址是一致的因为都指向同一个地址,但是 char 指针输出的值却出现了错误,是因为类型不同他们的跨度不同所造成的

从输出结果可以看到 :

        int 跨度从 0C——>10 为4个字节

        char 跨度从0C——>0D 为1个字节

#include <stdio.h>

int main()
{
    int a = 1000;
    int *p = &a;
    char *d = &a;
    printf("输出地址:\n%p\n%p\n", p, d);
    printf("输出值%d\n", *p);
    printf("输出值%d\n", *d);
    printf("++p = %p\n", ++p);
    printf("++d = %p\n", ++d);
    return 0;
}
输出地址:
000000000061FE0C
000000000061FE0C
输出值1000
输出值-24
++p = 000000000061FE10
++d = 000000000061FE0D

3.为什么需要用到指针

举例1

这里举一个简单的例子来说明

#include <stdio.h>

void change1(int data1, int data2)
{
    int temp;
    temp = data1;
    data1 = data2;
    data2 = temp;
}

void change2(int *data1, int *data2)
{
    int temp;
    temp = *data1;
    *data1 = *data2;
    *data2 = temp;
}
int main(void)
{
    int a1 = 5;
    int b1 = 10;
    change1(a, b); //未使用指针
    printf("未使用指针:a = %d b = %d\n", a, b);

    
    int a2 = 5;
    int b2 = 10;
    change2(&a, &b); //使用指针,需要输入地址
    printf("使用指针:a = %d b = %d\n", a, b);
    
    return 0;
}
未使用指针:a1 = 5 b1 = 10
使用指针:a2 = 10 b2 = 5

这里可以看到,我们封装了两个交换数值的函数,一个使用了指针一个未使用,从输出结果可以看到,调用未使用指针的函数,两个数的值是不会经行交换的

未使用指针:虽然传入的值是一样的,但是main函数里值的地址和change1函数中值的地址是不同的,交换只是在change1函数中开辟了一个内存空间经行执行,改变了局部变量,所以在main函数里调用不起作用,但是将 a 和 b 定义为全局变量,那么在main函数中也是可以生效的

使用指针:直接将地址传过来通过地址来间接访问,所以在change2函数中可以直接访问到main函数里的内存空间再经行交换,是有效的交换

举例2

带返回值的应用

int add1(int num)
{
    return ++num;
}

int add2(int *num)
{
    return ++(*num);
}

int main(void)
{
    int num1 = 10;     //未使用指针调用
    int re1;           //接收返回值
    int num2 = 10;     //使用指针调用
    int re2;           //接收返回值
    re1 = add1(num1);  //未使用指针
    re2 = add2(&num2); //使用指针
    printf("未使用指针num1 = %d,返回值re1 = %d\n", num1, re1);
    printf("使用指针num2 = %d,返回值re2 = %d\n", num2, re2);
    return 0;
}
未使用指针num1 = 10,返回值re1 = 11
使用指针num2 = 11,返回值re2 = 11

 可以看到不使用指针返回的时候只起一次作用,num1的值不会改变,下次调用返回值依然不会变

使用指针后num2的值发生了改变,下次调用函数返回值会继续加1

举例3

在单片机寄存器中使用,指向一个内存地址

unsigner int *p = (unsigner int *)0x00000000FED

二、指针和数组

1.概念

可以用一个指针变量指向一个数组元素

int a[5] = {1,2,3,4,5};

int *p;

p = &a[0];

 数组名 a 代表数组中首个元素 a[0] 的地址因此下面两个语句等价

p = &a[0];
p = a;

2.指针增量和数组的关系

遍历数组

#include <stdio.h>

int main(void)
{
    int arr[5] = {1, 2, 3, 4, 5};
    int *p;
    p = arr;
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("地址:%p,值:%d\n", p + i,*(p + i));
        printf("直接使用数组名-地址:%p,值:%d\n", arr + i, *(arr + i));
        printf("\n");
    }
    return 0;
}
使用指针来输出-地址:000000000061FDF0,值:1
直接使用数组名-地址:000000000061FDF0,值:1

使用指针来输出-地址:000000000061FDF4,值:2
直接使用数组名-地址:000000000061FDF4,值:2

使用指针来输出-地址:000000000061FDF8,值:3
直接使用数组名-地址:000000000061FDF8,值:3

这里指针 p+i 表示指针地址偏移 i 个 int 的跨度,也就是指向数组里的第 i 个数

通过指针来访问效率大于用下标来访问数组元素

指针和数组名的区别

        1)

        printf("%d\n", *p++); 是可以通过的,表示指针变量,可以改变

        printf("%d\n", *arr++); 编译错误,是一个指针常量,不能被改变

        2)

        printf("%d\n", sizeof(p));——输出8,操作系统用8个字节表示一个地址

        printf("%d\n", sizeof(arr));——输出20,数组里有5个数,4*5 = 20

3.函数指针数组结合使用

这种方法在开发过程中经常会遇到,常见的情况比如:串口发送一串数据data出去,需要发送的数据长度为len

#include <stdio.h>

void initArr(int *data, int len)  //这里len不需要用指针传入,len只是作为循环使用不需要改变
{
    int i;
    for (i = 0; i < len; i++)
    {
        printf("输入第%d个数据:\n", i + 1);
        scanf("%d", data);       //这里可以理解为 &(*data) 所以等同于 data
        data++;                  
    }
}
void printArr(int *data, int len)
{
    int i;
    for (i = 0; i < len; i++)
    {
        printf("%d", *data);//data为指针,指向main函数arry[0]的地址,*data表示取arry[0]的数值
        data++;             //指向下移
    }
}
int main(void)
{
    int arry[5];
    int len = sizeof(arry) / sizeof(arry[0]);
    initArr(arry, len);
    printArr(arry, len);
}
输入第1个数据:
5
输入第2个数据:
6
输入第3个数据:
6
输入第4个数据:
5
输入第5个数据:
8
56658

4.二维数组

定义一个3行4列的二维数组

int a[3][4] = { { 1, 2, 3, 4},
                { 5, 6, 7, 8},
                { 9,10,11,12}};

 a表示第一行的首地址

a+1指向第二行首地址,跨度为一整行,这里就是1,2,3,4加起来16个字节

a+2指向第三行首地址

a[0] ——> 1,2,3,4       

        a[0]表示列首地址指向1,这里相对于一维数组把a[0]整个看作数组名,a[0]+1指向2,跨度为4个字节

a[1] ——> 5,6,7,8

a[2] ——> 9,10,11,12

5.数组指针

数组指针是一个指针

#include <stdio.h>

int getData(int (*p)[4], int hang, int lie)
{
    int data;
    data = *(*(p + hang) + lie);
    return data;
}
void inputHL(int *hang, int *lie)
{
    printf("输入行和列:\n");
    scanf("%d%d", hang, lie);
    printf("\n");
}
int main(void)
{
    int arr[3][4] = {{11, 22, 33, 44},
                     {55, 66, 77, 88},
                     {99, 10, 11, 12}};
    int hang, lie;
    int data;
    inputHL(&hang, &lie);
    data = getData(arr, hang, lie);
    printf("%d行%d列的值是:%d\n", hang, lie, data);
}
输入行和列:
0
3

0行3列的值是:44

这里我们来讲一下 data = *(*(p + hang) + lie)

我们输入hang为1 ,lie 为 1

p指向的第一行首地址

p + 1指向第二行首地址

*(p + 1)对其经行解引用指向第二行第一个元素的首地址,也就是列的首地址

*(p + 1) + 1表示列加一个跨度,指向第二行第二列元素的地址

*(*(p + 1) + 1)最后解引用,得到第二行第二列元素的值

6.指针数组

6.1定义

一个数组,若其元素均为指针类型数据,称为指针数组

指针数组中的每一个元素都存放一个地址,相当于一个指针变量

下标01234
元素int *int *int *int *int *
int *p[4];

 6.2举例

#include <stdio.h>

int main(void)
{
    int i;
    //定义一个指针数组
    char *p[4] = {
        "12345",
        "67890",
        "abcde",
        "fghijk"};
    for (i = 0; i < 4; i++)
    {
        printf("%s\n", p[i]);
    }
    return 0;
}
12345
67890
abcde
fghijk

7.二级指针

#include <stdio.h>

int main(void)
{
    int data = 100;
    int *p = &data;
    printf("data的地址时:%p\n", &data);
    printf("p保存data的地址:%p,内容是%d\n", p, *p);

    int **p2;
    p2 = &p;
    printf("p2保存p的地址:%p\n", p2);
    printf("*p2是:%p\n", *p2);
    printf("**p2来访问data:%d\n", **p2);
    return 0;
}
data的地址时:000000000061FE14
p保存data的地址:000000000061FE14,内容是100
p2保存p的地址:000000000061FE08
*p2是:000000000061FE14
**p2来访问data:100

8.二级指针和二维数组

三、指针和函数

1.函数指针

1.1 定义:

1)如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针

2)函数名就是地址
 

int change(int a,int b);        //定义一个函数

int (*p)(int a,int b);          //定义一个函数指针,类型要和函数一致

1.2 举例

下面举个简单的例子来验证下

#include <stdio.h>

void changenum(int *a, int *b)
{
    int c;
    printf("hello world\n");
    c = *a;
    *a = *b;
    *b = c;
}

int main()
{
    int one, two;
    one = 5;
    two = 10;
    void (*p)(int *a, int *b); //定义一个函数指针变量,类型和函数保持一致
    p = changenum;             //指向函数,函数名就是函数的地址
    (*p)(&one, &two);          //调用函数指针,间接访问函数
    printf("交换后one = %d,two = %d\n", one, two);
    return 0;
}
hello world
交换后one = 10,two = 5

可以和指针变量做对比,可以很好的理解

指针变量函数指针
int a = 10;//定义一个变量void change(int a);//定义一个函数

int *p ;             //定义一个int指针,

p = &a;            //指向a的地址

printf("%d",*p);//通过指针间接访问 a

void (*p)(int a);       //定义一个函数指针

p = change;            //指向函数的地址

(*p)();               //通过指针间接访问函数

1.3函数指针作为入口参数

        这里举一个例子,实现内容为:输入两个值num1  num2,再输入一个控制命令cmd,输入1将取出最大值,输入2取最小值,输入3求和

#include <stdio.h>

//返回两数最大值
int getMax(int num1, int num2)
{
    int re;
    re = num1 > num2 ? num1 : num2;
    return re;
}
//返回两数最小值
int getMin(int num1, int num2)
{
    int re;
    re = num1 < num2 ? num1 : num2;
    return re;
}
//返回两数和
int getAdd(int num1, int num2)
{
    int re;
    re = num1 + num2;
    return re;
}

int result(int num1, int num2, int (*p)(int, int)) //函数指针做参数传入
{
    int re;                //用于接收返回的结果
    re = (*p)(num1, num2); //调用函数指针操作两个数
    return re;             //返回结果
}

int main(void)
{
    int a;
    int b;
    int cmd;
    int ret;
    int (*p)(int, int); //定义一个函数指针
    printf("输入两个整数:\n");
    scanf("%d,%d", &a, &b);
    printf("选择要做的操作:\n1取最大值\n2取最小值\n3求和\n");
    scanf("%d", &cmd);
    switch (cmd)
    {
    case 1:
        p = getMax; //函数指针指向取最大值函数
        break;
    case 2:
        p = getMin; //函数指针指向取最小值函数
        break;
    case 3:
        p = getAdd;
        break;
    default:
        break;
    }
    ret = result(a, b, p); //入口参数为指针
    printf("返回结果为:%d\n", ret);
}
输入两个整数:
12,13
选择要做的操作:
1取最大值
2取最小值
3求和
3
返回结果为:25

1.4函数指针作为返回值

四、字符串部分

1.定义

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a[3] = {11, 22, 33};                 //定义一个数组
    char c = 'a';                            //定义一个字符
    char str[5] = {'a', 'b', 'c', 'd', 'e'}; //定义一个字符串数组
    printf("——1——\n");
    //遍历字符串数组
    for (int i = 0; i < sizeof(str) / sizeof(str[0]); i++)
    {
        printf("%c", str[i]);
    }
    printf("\n——2——\n");
    char str2[5] = "joker"; //定义一个字符串数组

    for (int j = 0; j < sizeof(str2) / sizeof(str2[0]); j++)
    {
        printf("%c", str2[j]);
    }
    printf("\n——3——\n");
    char str3[] = "jokersjwkjad"; //数组大小不写
    for (int j = 0; j < sizeof(str3) / sizeof(str3[0]); j++)
    {
        printf("%c", str3[j]);
    }
    printf("\n——4——\n");

    //前三种定义字符串不是很推荐,下面是用的指针指向字符串数组,在开发中通常用这种
    //如果指针操作不当,容易造成段错误sigment error
    char *p = "qwkdjhqwjkd  qwkldjqlkwd jhnjsda";
    printf("%s\n", p); //字符串用格式占位符%s表示,不需要用i的下标遍历
    return 0;
}
——1——
abcde
——2——
joker
——3——
jokersjwkjad
——4——
qwkdjhqwjkd  qwkldjqlkwd jhnjsda

2.字符串的存储方式

#include <stdio.h>
#include <stdlib.h>


int main()
{
    char str[3] = "123";
    printf("数组的大小:%d\n", sizeof(str));
    printf("数组一个元素的大小:%d\n", sizeof(str[0]));
    printf("数组的长度:%d\n", sizeof(str) / sizeof(str[0]));
    printf("%s\n", str);

    printf("——————————————————————————\n");
    char str2[] = "123";
    printf("数组的大小:%d\n", sizeof(str2));
    printf("数组一个元素的大小:%d\n", sizeof(str2[0]));
    printf("数组的长度:%d\n", sizeof(str2) / sizeof(str2[0]));
    printf("%s\n", str2);

    return 0;
}
数组的大小:3
数组一个元素的大小:1
数组的长度:3
123
—————————————————————————
数组的大小:4
数组一个元素的大小:1
数组的长度:4
123

可以看到两组的存储空间大小是不一样的

str[] = {1,2,3}        系统会自动分配大小,并且在末尾加  '\0' 结束符,所以会多出一个字符

3.sizeof 、 strlen、memset、mencpy 的使用

3.1sizeof和strlen

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    int a[] = {1, 2, 3};
    printf("数组a的元素个数是:%d\n", sizeof(a) / sizeof(a[0]));

    char a2[120] = "hello";
    //使用sizeof
    printf("数组a2的元素个数:%d\n", sizeof(a2) / sizeof(a2[0]));
    //使用strlen
    printf("数组元素的个数:%d\n", strlen(a2));
    return 0;
}
数组a的元素个数是:3
数组a2的元素个数:120
数组元素的个数:5

可以看到使用sizeof 得到的是定义时字符串数组的大小,这里是[120]  占120个字节

使用strlen得到的是实际字符串中内容的大小,这里是 hello 的大小,占5个字节

3.2memset 和 memcpy

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
        char str[] = "I love FishC.com!";
        char *ptr;                                        //野指针,需要开辟一个空间
        int length = sizeof(str);

        printf("length = %d\n", length);

        ptr = (char *)malloc(length * sizeof(char));       //给野指针开辟一个空间
        if (ptr == NULL)                                   //如果申请内存不成功
        {
                exit(1);
        }

        memset(ptr, 0, length);
        memcpy(ptr, str, length);

        printf("%s\n", ptr);

        return 0;
}

 memcpy是操作内存空间,strcpy是操作字符串

函数原型

void *memcpy(void *dest, const void *src, size_t n);

注意:memcpy 函数从 src 指向的内存空间拷贝 n 个字节到 dest 指向的内存空间。src 和 dest 指向的内存区域不能出现重叠,否则应该使用 memmove 函数。

#include <stdio.h>
#include <stdio.h>
#include <string.h>
int main()
{
    char a[7] = "{'\0'}";
    char b[7] = "1234567";
    char *c = "1234567";
    memset(a, '\0', 7);
    memcpy(a, b, 7); // a和b都是指针常量,操作会出错,要使用memmove
    puts(a);
    memset(a, '\0', 7);
    memcpy(a, c, 7); //a是指针常量,c是指针变量,可以经行操作
    puts(a);
    memset(a, '\0', 7);
    memmove(a, b, 7); //a和b都是指针常量,可以直接操作
    puts(a);

    return 0;
}

4.其他常用字符串操作函数

4.1puts()和 gets();    

  puts()输出字符串,自动换行

  gets()接收字符串

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *p = "我是大帅哥";
    puts(p); //自动换行
    printf("%s\n", p);

    //char p[128] = {'\0'};     //初始化字符串数组

    char *p2;                 //野指针,会出错
    p2 = (char *)malloc(128); //1.开辟一个空间  2.一旦用了malloc就要注意内存泄漏问题  3.会失败,要注意返回值
    if (p2 == NULL)
    {
        printf("申请内存失败\n");
        exit(-1);
    }
    memset(p2, '\0', 128); //初始化字符串数组
    printf("输入字符串:\n");
    //scanf("%s", p2);
    gets(p2); //
    puts(p2);

    return 0;
}
我是大帅哥
我是大帅哥
输入字符串:
asdlkhjaskldjasdkj
asdlkhjaskldjasdkj

4.2strcpy 拷贝字符串数组

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char strDest[128] = {'\0'};
    char *pstr = "我是大帅哥";
    strcpy(strDest, pstr);
    printf("拷贝完成后%s\n", strDest);

    memset(strDest, '\0', 128); //下次调用最好重新初始化下
    strcpy(strDest, "你是大帅哥");
    puts(strDest);

    memset(strDest, '\0', 128);
    strncpy(strDest, "12345678", 5); //可以选择拷贝的长度
    puts(strDest);
    return 0;
}
拷贝完成后我是大帅哥
你是大帅哥
12345

4.3strcat 函数用于连接两个字符串

将源字符串拷贝并连接到目标数组存放的字符串后边,此过程将覆盖第一个参数的结束符 '\0'。

两个参数的位置不应该重叠。

#include <string.h>
...
char *strcat(char *dest, const char *src);

参数

含义

dest指向用于存放字符串的目标数组,它应该包含一个字符串,并且提供足够容纳连接后的总字符串长度的空间(包含结束符 '\0')
src指向待连接的源字符串,该参数不应该与 dest 参数指向的位置发生重叠

4.4strncmp 函数用于比较两个字符串的前 n 个字符

该函数从第一个字符开始,依次比较每个字符的 ASCII 码大小,发现两个字符不相等或抵达结束符('\0')为止,或者前 n 个字符完全一样,也会停止比较。

#include <string.h>
...
int strncmp(const char *s1, const char *s2, size_t n);

返回值

含义

< 0字符串 1 的字符小于字符串 2 对应位置的字符
0两个字符串的内容完全一致
> 0字符串 1 的字符大于字符串 2 对应位置的字符

4.5strchr

函数原型

char *strchr(const char *str, int c)

参数

  • str-- 要被检索的 C 字符串。

  • c-- 在 str 中要搜索的字符。

功能

在参数str所指向的字符串中搜索第一次出现字符c(一个无符号字符)的位置。

返回值

返回一个指向该字符串中第一次出现的字符的指针,如果字符串中不包含该字符则返回NULL空指针。

示例:

#include <stdio.h>
#include <stdio.h>
#include <string.h>

int main()
{
    char *str = "joker";
    char c = 'k';
    char *p = NULL;
    p = strchr(str, c);

    if (p == NULL)
    {
        printf("没有找到");
    }
    else
    {
        printf("找到相关的字符");
        puts(p);
    }
    return 0;
}
找到相关的字符ker

4.6strstr

strstr()函数用于找到子串在一个字符串中第一次出现的位置,在string.h头文件中。
函数原型:char *strstr(const char *str1, const char *str2)
str1是总串,str2是需要匹配的第一个字串位置,返回值为char * 类型。
 

#include <stdio.h>
#include <string.h>

int main()
{
    char *str1 = "123456789";
    char *str2 = "456";
    char *p;
    p = strstr(str1, str2);
    puts(p);
    return 0;
}
456789

五、结构体指针

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//如果要使用结构体指针,就不能用点运算符访问结构体中的变量,要用 ->
//指针要注意是否是也只在或者NULL

struct Student
{
    int score;
    char name[128];
    /* data */
} stu1;

int main()
{
    stu1.score = 100;
    strcpy(stu1.name, "张三");
    printf("名字:%s,分数:%d\n", stu1.name, stu1.score);
    struct Student *p; //野指针
    p = (struct Student *)malloc(sizeof(struct Student));
    p->score = 98;
    strcpy(p->name, "李四");
    printf("名字:%s,分数:%d\n", p->name, p->score);

    free(p); //释放内存

    p = &stu1; //指针是存放地址的变量,之前是指向malloc那块空间,现指针变量存放的是stu1的地址
    printf("名字:%s,分数:%d\n", p->name, p->score);
    return 0;
}
名字:张三,分数:100
名字:李四,分数:98
名字:张三,分数:100

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值