欢迎光临
我们一直在努力

C语言进阶第六弹——指针(持续更新ing)

1. 指针的特点

  • 编写代码更加简洁高效
  • 指针可以直接访问内存,要访问合法的内存地址(不要越界访问)
  • 指针可以直接操作硬件(51单片机,ARM开发板)
  • 变量、数组:为了数据存储
    指针:为了高效访问数据,间接访问

2. 基本概念

内存(memory)

  • 程序运行后,数据暂存的场所。32bit下,0~4G。
  • C语言中,操作内存的时候,最小单位是1byte(字节)。
  • code(代码段),data(数据段),heap(堆),map/share(C库函数),stack(栈:局部变量、函数参数、返回地址)

地址

  • 由于地址空间相对较大,C语言中最小操作单位1byte。
  • 为了方便访问内存,给每个内存都分配了一个编号,叫内存的地址。

指针(指针变量)

  • 专门存储地址的变量,叫指针变量,也叫指针。

3. 指针相关运算符

1)& 取地址运算符

&(取地址运算符):获得变量在内存空间的地址

  • 只能给左值(变量)取地址
  • int a;&a;&a是一个表达式。 它的值表示的是这个变量在内存空间的首地址。类型是int *。

double num = 0;

#// 0x123456   double *

2)* 解引用

只能是指针类型才能解引用,先找到对应的内存地址,获得内存地址空间中的内容。

int a = 20;

int *p = &a;// int *  不是解引用,是类型说明符,意思是p里只能装int型的地址。

a;//20 直接访问

*p;//20 解引用   值:对应内存空间中的数据,类型 int 

*p = 10; //给a写入数据,左值

int b = *p;// 右值,读取a中的数据

*&a;// 20        *和&是相反操作的运算符

int p;

int *p;// p        int ——>int*

*p;// int * ——> int

4. 指针的定义

基类型 * 指针名;

int* p1;         // * 不是解引用 类型说明符 int *
char *p2;         // char*
double * p3;         //double *
float* p4;         //float *

5. 指针的初始化

指针需要初始化后,才可以使用。指针必须和某个内存区域关联后,才可使用。

野指针:

未经初始化的指针,或指针指向的内存空间,是一个已经被释放的空间的指针。

int *p;// p中存储的地址,是一个随机值。从逻辑上说,这个指针不能读也不能写。

空指针:

NULL(宏定义),在stdio.h中。

int *p1 = NULL;//空指针,NULL == ((void*)0)        指针已初始化,稍后才会关联一个 变量的地址。未关联变量地址前,也不能读写。

int main()
{
int a = 5;
printf("address:%p\\n", &a);

int *p1;
char *p2;
double *p3;
float * p4;

// printf("p1:%d\\n", *p1);
// 野指针,可能会段错误

//指针大小
printf("int *p1 size:%lu\\n", sizeof(p1));
printf("int *p2 size:%lu\\n", sizeof(p2));
printf("int *p3 size:%lu\\n", sizeof(p3));
printf("int *p4 size:%lu\\n", sizeof(p4));

int *p5 = NULL; //空指针
int *p6 = &a;

printf("p6:%p\\n", p6);
printf("p6:%p\\n", &a);

printf("a value:%d\\n", *p6);//通过指针,间接读取a变量的值

*p6 = 20;//修改变量
printf("new a value:%d\\n", *p6);//通过指针,间接读取a变量的值
printf("a value :%d\\n", a);//直接访问a变量的值

return 0;
}
/*
输出
address:0x7ffcef3a9584
int *p1 size:8
int *p2 size:8
int *p3 size:8
int *p4 size:8
p6:0x7ffcef3a9584
p6:0x7ffcef3a9584
a value:5
new a value:20
a value :20
*/

6. 指针的算术运算

+ – * / % ++ —

int a = 20;

int *p = &a;

int *p2 = p1+1;

+        

p+1       

在p指针指向的地址的基础上,再加一个sizeof(基类型)个字节(如int型就加4byte)。那么p指针就指向当前元素的下一个元素。

如果指针需要+1,-1, 一般和数组操作的时候,才会使用。

+:p+1(数组)       

两指针相加,无意义        p(存储一个变量的地址)+q(存储另外一个变量的地址)==无意义

– 

p – 1       

p应该指向一个数组,操作完后,指针应该落在数组范围以内。

int a[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = a + 1;
int *q = a + 5;
printf("q-p %ld\\n", q-p);

两个地址相减,前提这两个指针向同一个数组。

意义:相差的元素个数。 如果这两个指针其他不同的地方,相减无意义。

* / % 

对指针,无意义

++和–

++        p++        

先获得当前地址,加上sizeof(基类型)。计算的结果再写回指针变量本身。

—        p–

先获得当前地址,减去sizeof(基类型)。计算的结果再写回指针变量本身

不同类型指针再加1:

  • char *        偏移量1byte  sizeof(基类型)
  • int *        偏移量4byte
  • float *        偏移量4byte
  • double *        偏移量8byte

不同类型执行解引用操作:

char *         从指针存储地址开始,往后(地址变大的方向)取1个字节的内容

int *         从指针存储地址开始,往后(地址变大的方向)取4个字节的内容

float *        从指针存储地址开始,往后(地址变大的方向)取4个字节的内容

double *        从指针存储地址开始,往后(地址变大的方向)取8个字节的内容

char str[100] = "hello";

char * p = str;

*p

其他操作

指针在使用的时候,有两个概念,一个是指针变量本身,一个是指针指向的内容

对指针变量的修改

int main()
{
int b[5] = {0};
int a[5] = {0};
int *p = a;

*p = 123; //修改指针指向的内容

p = b; // 修改指针的指向

return 0;
}

7. 指针和数组

1)数组的数组名

  • 数组的数组名是一个指向数组中第一元素的指针常量。
  • a 存储的地址,不能发生变化。
  • 数组名的本质就是一个指针常量(加了一些限制:const)。

数组名与普通指针访问数据的注意事项:

int main()
{
char str[100]="hello";
char * p = str;
/*
while(*p)
{
printf("%c",*p);
p++; // 可以++,– 操作, p本身被修改
}
*/
/*
while(*str)
{
printf("%c",*str);
str++; // 数组名一个指针常量,本身不可以++,– 。当数组定义好后, str 是一个地址
标号,和开辟的内存空间的编号,终身绑定。
}*/
show(str);
}

2)int * 和 int[]  

int * 约等于 int[],只有以下两点不同用法:

  • sizeof
    sizeof 数组:int a[50];sizeof(a) == sizeof(int) × 50byte
    sizeof 指针:int *p;sizeof(p) == 8byte 任意指针都是8个字节(64bit)

  • int * 执行取地址操作,会变成int** ,是二级指针,即存储地址的地址。
    int[] 执行取地址操作,即int(*p)[],是数组指针,指向二维数组中的一行。
  • int a[10] = {1,2,3,4};        //2000

    int b[10] = {2,3,4,5,6,7};        //3000

    a = b;        //错误,a中存储的地址不能改变。

    3)使用指针访问,数组的元素

    int a[5]={1,2,3,4,5};
    printf("arrray addr %p\\n",a); //a就是一个指针。 存储的是 a[0]地址
    printf("a[0] %p\\n",&a[0]);
    printf("a[1] %p\\n",&a[1]);

    int *p =a ; // p指针和 数组进行关联 , p 中存储了a[0]的地址。
    printf("a[0] %d\\n",a[0]);
    printf("point *(p+0) %d\\n",*(p+0));
    printf("*(a+0) %d\\n",*(a+0));
    printf("point p[0] %d\\n",p[0]);
    printf("a[0] addr %p\\n",&a[0]);
    printf("point addr *(p+0) %p\\n",&(*(p+0)));
    printf("*(a+0) addr %p\\n",&(*(a+0)));
    printf("point addr p[0] %p\\n",&p[0]);

    printf("a[1] %d\\n",a[1]);
    printf("point a[0] %d\\n",*(p+1));

    访问方式
    a[n] —> *(p+n) –>*(a+n)—> p[n]

    8. 函数使用指针作为参数

    1)值传递

    形参是实参的复制品,只能读取实参的数据,在被调函数内部,不能通过修改形参而改变实参。基本数据类型的变量。

    2)地址传递

    形参会接收到实参传递过来的地址,就可以在被调函数的内部,通过间接访问的方式,通过修改形参,进而修改实参。

    9. 字符数组的传参——地址传参 

    使用指针访问数据,比使用数组方式访问要高效、简洁。

    mystrlen

    int mystrlen(char *p1)
    {
    int len = 0;
    while(*p1)
    {
    len++;
    p1++;
    }
    return len;
    }

    mystrcopy

    int mystrcpy(char *p2, char *p1)
    {
    while(*p2 = *p1)
    {
    printf("%c", *p2);
    p1++;
    p2++;
    }
    }

    mystrcmp

    int mystrcmp(char *p1, char *p2)
    {
    while(*p1 == *p2 && *p1)
    {
    p1++;
    p2++;
    }
    return *p1 – *p2;
    }

    mystrcat

    int mystrcat(char*p, char*q)
    {
    while('\\0'!=*p)
    {
    p++;
    }
    while(*p = *q)
    {
    p++;
    q++;
    }
    }

    mystrrev

    int strrevert(char * str)
    {
    int len = strlen(str);
    int i = 0 ;
    for(i=0;i<len/2;i++)
    {
    char c = *(str+i);
    *(str+i) = *(str+len-1-i);
    *(str+len-1-i) =c;

    }
    return 0;
    }

    10. 指针函数和函数指针

    1)指针函数

    函数的返回值为指针类型的函数,叫指针函数。

    • 注意:不要返回局部作用的数据(局部变量、局部数组)作为另一个函数的参数。

    mystrncpy

    char *mystrncpy(char *dst, char *src, int n)
    {
    char *temp = dst;
    while((*dst = *src) && –n)
    {
    dst++;
    src++;
    }
    if(0 == n)
    {
    *(dst+1) = '\\0';
    }
    return temp;
    }
    int main()
    {
    char src[50] = {0};
    char dst[50] = {0};
    printf("input str:");
    gets(src);
    char *p = mystrncpy(dst, src, 5);
    printf("strncopy:%s\\n", dst);
    return 0;
    }
    /*
    input str:hello
    strncopy:hello
    */

    mystrncat

    #include<stdio.h>
    char *mystrncat(char *str1, char *str2, int n)
    {
    while(*str1 != '\\0')
    {
    str1++;
    }
    while((*str1 = *str2) && –n)
    {
    str1++;
    str2++;
    }
    return str1;
    }

    int main()
    {
    char str1[50] = {0};
    char str2[50] = {0};
    printf("string1:");
    gets(str1);
    printf("string2:");
    gets(str2);
    mystrncat(str1, str2, 4);
    printf("contanct:%s\\n", str1);

    return 0;
    }
    /*
    string1:respon
    string2:sibility
    contanct:responsibi
    */

    mystrncmp

    #include<stdio.h>
    int mystrncmp(char *str1, char *str2, int n)
    {
    while(*str1==*str2 && –n && *str1!='\\0')
    {
    str1++;
    str2++;
    }
    return *str1 – *str2;
    }

    int main()
    {
    char str1[50] = {0};
    char str2[50] = {0};
    printf("input string1:");
    gets(str1);
    printf("input string2:");
    gets(str2);
    int ret = mystrncmp(str1, str2, 4);
    if(ret != 0)
    {
    printf("4th word is not same\\n");
    }
    else
    {
    printf("4th word is same\\n");
    }

    return 0;
    }
    /*
    input string1:hhhhhhhhh
    input string2:hhhhkkkkk
    fore4 word is same

    input string1:hhhhhh
    input string2:hhhkhh
    fore4 word is not same
    */

    2)函数指针

    本质是指针,存储某一个函数的地址。函数名本质是一个指针,存储了一个地址,指向代码段中,对数据操作的代码。

    int add(int a, int b)

    {

    }

    int (*p) (int, int) = NULL; // 定义指针,初始化

    p = add; // 函数add的地址给到指针p

    int ret = p(a, b);

    printf("%d", ret);

    应用:降低代码的耦合度

    回调函数、函数设计者、函数的调用者:

    • 作为函数设计者,希望显示数据的时候,可以更加多种规则显示。所有需要主调者传入规则函数。需要使用函数指针进行解耦。这里的调函数就是回调函数

    案例:低耦合调用加减乘除

    #include<stdio.h>
    int add(int a, int b)
    {
    return a + b;
    }
    int sub(int a, int b)
    {
    return a – b;
    }
    int mul(int a, int b)
    {
    return a * b;
    }
    int div(int a, int b)
    {
    return a / b;
    }
    int main()
    {
    int num1 = 0;
    int num2 = 0;
    printf("input num1:");
    scanf("%d", &num1);
    printf("input num2:");
    scanf("%d", &num2);

    int (*p) (int, int) = NULL;

    int choose = 0;
    printf("input: 1(+) or 2(-) or 3(*) or 4(/)\\n");
    scanf("%d", &choose);
    switch(choose)
    {
    case 1:
    p = add;
    break;
    case 2:
    p = sub;
    break;
    case 3:
    p = mul;
    break;
    case 4:
    p = div;
    break;
    default:
    printf("input error\\n");
    }

    int ret = p(num1,num2);
    printf("result:%d\\n", ret);

    return 0;
    }
    /*
    input num1:4
    input num2:2
    input: 1(+) or 2(-) or 3(*) or 4(/)
    4
    result:2
    */

    12. 二级指针

    概念:指向指针变量的指针。

    int a =20; // int

    int *p = &a; // int* = int *

    int** q = &p; // int **

    printf("&a is %p\\n",&a);

    printf(" p->a is %p\\n",p);

    printf("q->p is %p\\n",q);

    printf("a is %d\\n",a);

    printf("*p is %d\\n",*p); // int * –>* ->int

    printf("*q is %p\\n",*q);// &a -> int*

    printf("**q is %d\\n",**q);// &a -> int

    应用:

  • 函数调用的地址传递中,需要改变指针变量本身,用二级指针(改变指针的指向);如果需要改变指针指向的内容,用一级指针。
  • 函数传递参数的过程中,char**(二级指针) 与 char*[](指针数组),兼容类型。
  • 13. const 指针

    const 常量

    int a = 20;

    const int b = 10; // b 叫作只读变量

    const int c; // 错误

    c = 20; // 因为是只读变量,需要在定义的时候就初始化,否则后续赋值会报错。

    //常量指针

    //指针指向的内容不能发生变化,p1与p2等价
    const int *p1; 
    int const *p2;

    //指针常量

    //指针本身不能变化,指针的指向也不能变

    int *const p3;

    //指向常量的常量指针

    //指针本身不能变,指针指向的内容也不能变

    const int * const p4;

    char str1[100] = "hello";

    char str2[100] = "world";

    const char *p = str; // 常用

    p[0] = 'a'; // 错误,不能通过p修改str的内容

    str1[0] = 'a'; // 正确

    p = str2; // 正确,表达内容不能变,没有表示本身不能变

    char* const p2 = str; // 定义的时候,就要初始化

    p2[0] = 'a'; // 正确,指针本身不能变,指向的内容可以修改

    p2 = str2; // 错误,p2本身不能变化

    const char *const p3 = str; //指针本身不能变,内容也不能变

    // p3[0] = 'a';

    // p3 = str2;

    赞(0)
    未经允许不得转载:171主机测评 » C语言进阶第六弹——指针(持续更新ing)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址