• C语言基础(16)-指针


    一.指针的相关概念

    1.1 指针变量

    指针是一个变量,存放的是一个地址,该地址指向一块内存空间。

    例:

    int a = 10;
    
    int *p = &a; // 定义一个指针变量p,&符号可以取得一个变量在内存当前中的地址。
    
    *p = 5; // 修改指针所指的内存数据为5

    1.2 无类型指针

    定义一个指针变量,但不指定它指向具体哪种数据类型,可以通过强制转化将void *转化为其它类型指针,也可以用(void *)将其它类型指针强制转化为void *类型指针。

    1.3 NULL

    NULL在C语言中的定义为(void *)0,空指针就是指向了NULL的指针变量。

    int *p = &a;
    p = NULL; // p就是一个空指针

    1.4 野指针

    野指针就是没有指向任何有效地址的指针变量。在代码中应当尽量避免出现野指针,如果一个指针不能确定指向任何一个变量的地址,那么请将这个指针变成空指针。

    二.使用指针时的一些注意点

    2.1 指针的兼容性

    指针之前赋值比普通数据类型赋值检查更为严格,如:不可以将一个double * 赋值给一个int。且指针变量只能存放地址,也不能将一个int类型变量直接赋值给一个指针。

    2.2 常量指针与指针常量

    记忆方法:* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变

    int a =3;
    int b = 1;
    int c = 2;
    int const *p1 = &b;//const 在前,定义为常量指针
    int *const p2 = &c;//*在前,定义为指针常量 

    常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。   

        p1 = &a是正确的,但 *p1 = a是错误的。
    指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
        p2= &a是错误的,而*p2 = a 是正确的。

    2.3 指针所占的位数

    指针所占的位数不会因类型不同而不同,如int *p与double *q 在同一操作系统下所占的位数始终是相同的,只跟操作系统的位数(x64/x86)相关。

    2.4 指针与数组之间的关系

    在C语言中数组的名字就是数组的地址,数组的地址也等价于数组第一个元素的地址

    例:

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    
    
    void main() {
        
        int a[3] = {3,4,5};
        int *p = a;
        int *q = &a[0];
        printf("变量p存放的地址为:%p
    变量q存放的地址为:%p
    ",p,q);
    
        system("pause");
    }

    也可以通过指针来遍历当前数组元素:

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    
    
    void main() {
        
        int a[3] = {3,4,5};
        int *p = a;
        //int *q = &a[0];
        //printf("变量p存放的地址为:%p
    变量q存放的地址为:%p
    ",p,q);
        for (int i = 0; i < 3; i++) {
            printf("第%d个元素为:%d
    ",i,p[i]);
        }
        system("pause");
    }

    三.指针的其它用法

    3.1 指针的运算

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    
    
    // 指针的运算
    void point_test_1();
    
    // 清除数组中的值(p的值发生改变)
    void clearArrayValueByPoint();
    
    // 清除数组中的值(p的值不发生改变)
    void clearArrayValueByPoint2();
    
    // 使用指针来进行冒泡排序
    void sortByPoint();
    
    // 任何一种数据类型都是char的数组
    void point_test_2();
    void point_test_3();
    
    // 表示IP地址 192.168.111.123
    void ipAddressByPoint();
    
    void main() {
        
        ipAddressByPoint();
        system("pause");
    }
    
    
    
    // 指针的运算
    void point_test_1() {
    
        int *p1;
        char *p2;
        int a1 = 0;
        char a2 = 0;
        p1 = &a1;
        p2 = &a2;
        printf("%p,%p
    ", p1, p2);
    
        p1++; // 位移了4个字节,4个sizeof(int)
        p2++; // 位移了1个字节,1个sizeof(char)
        printf("%p,%p
    ", p1, p2);
    
        p1 += 4; // 位移了4*sizeof(int) = 16
        p2 += 4; // 位移了4*sizeof(char) = 4
        printf("%p,%p
    ", p1, p2);
    
        p1 -= 2; // 向前移动了2*sizeof(int)
        p2 -= 2; // 向前移动了2*sizeof(char)
        printf("%p,%p
    ", p1, p2);
    
    }
    
    
    // 清除数组中的值(p的值发生改变)
    void clearArrayValueByPoint() {
    
        int array[10] = {1,2,3,4,5,6,7,8,9,10};
        
        int *p = array;
    
        for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
            *p = 0;
            p++;
        }
    
        for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
            printf("array[%d]=%d
    ", i, array[i]);
        }
    }
    
    
    
    // 清除数组中的值(p的值不发生改变)
    void clearArrayValueByPoint2() {
    
        int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
        int *p = array;
    
        for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
            *(p + i) = 0; // 循环完成之后,p仍然指向首元素地址
        }
    
        for (int i = 0; i < sizeof(array) / sizeof(int); i++) {
            printf("array[%d]=%d
    ", i, p[i]); // 因为此时p仍然指向首元素地址,所以可以使用p[i]来输出各个元素
        }
    }
    
    
    // 使用指针来进行冒泡排序
    void sortByPoint() {
    
        int array[3][4] = { { 32, 45, 56, 13 }, { 65, 43, 132, 54 }, {12,32,43,55} };
        int *p = array; // array可理解成里面有12个元素的array[12],任何一个多维数组都可以理解成一个一维数组
    
        // 排序
        for (int i = 0; i < 12; i++) {
            
            for (int j = 1; j < 12-i;j++) {
                if (p[j] < p[j-1]) {
                    int tmp = p[j];
                    p[j] = p[j - 1];
                    p[j - 1] = tmp;
                }
            }
        }
    
        // 输出
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 4; j++) {
                printf("array[%d][%d]=%d	",i,j,array[i][j]);
            }
            printf("
    ");
        }
    
    }
    
    
    // 任何一种数据类型都是char的数组
    void point_test_2() {
    
        int a = 0x12345678;
        unsigned char *p = (char *)&a;
        printf("%x, %x, %x, %x
    ",p[0],p[1],p[2],p[3]);
    
    }
    
    
    void point_test_3() {
    
        int a = 0x12345678;
        unsigned char *p = (char *)&a;
        printf("%x, %x, %x, %x
    ", p[0], p[1], p[2], p[3]);
        p[1] = 0;
        printf("%x
    ",a);
    
    }
    
    
    // 表示IP地址 192.168.111.123
    void ipAddressByPoint() {
    
        unsigned int a = 0;
        unsigned char *p = (char *)&a;
        p[0] = 123;
        p[1] = 111;
        p[2] = 168;
        p[3] = 192;
        printf("%u
    ",a);
    
    }

    3.2 多级指针

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    
    // 多级指针
    void multiPoint();
    
    // 二级指针
    void multiPoint1();
    
    // 三级指针
    void multiPoint2();
    
    void main() {
        
        multiPoint2();
        system("pause");
    }
    
    
    
    // 多级指针
    void multiPoint() {
    
        int *a[10] = { 0 };
        int **p = a;
        int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
        for (int i = 0; i < 10; i++) {
            a[i] = &array[i]; // 将指针数组中的每一个指针指向数组中的每一个元素
        }
    
        for (int i = 0; i < 10; i++) {
            printf("array[%d]=%d
    ", i, *p[i]); // 取出每个指针对应的数组内容
        }
    }
    
    // 二级指针
    void multiPoint1() {
    
        int a = 10;
        int *p = &a;
        int **q = &p; // 二级指针不能直接指向int的地址,而是指向一个int*的地址
        // 修改a的值
        **q = 100;
        printf("a=%d
    ",a);
    }
    
    
    // 三级指针
    void multiPoint2() {
    
        int a = 10;
        int *p = &a;
        int **q = &p; // 二级指针不能直接指向int的地址,而是指向一个int*的地址
        int ***z = &q;
        // 修改a的值
        ***z = 100;
        printf("a=%d
    ", a);
    }

    3.3 指针变量当做函数的参数

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    
    // 将指针变量用作函数的参数
    void pointAsFunctionArgus(int *a);
    
    // 交换两个数的值
    void myswap(int *a1, int *a2);
    
    void main() {
        
        //int a = 10;
        //// 将a的值+1
        //pointAsFunctionArgus(&a);
        //printf("a=%d
    ",a);
    
        int a = 10;
        int b = 20;
        // 交换a,b的值
        myswap(&a,&b);
        printf("a=%d
    b=%d
    ",a,b);
    
        system("pause");
    }
    
    // 交换两个数的值
    void myswap(int *a1, int *a2) {
    
        int tmp = *a1;
        *a1 = *a2;
        *a2 = tmp;
    
    }
    
    // 将指针变量用作函数的参数
    void pointAsFunctionArgus(int *a) {
        (*a)++;
    }

    3.4 数组名作为函数参数

    当数组名作为函数参数时,C语言将数组名解释为指针,以下三种写法等价:

    int func(int array[10]);

    int func(int array[]);

    int func(int *array)

    如果数组名作为函数的参数,那么这个就不是数组了,而是一个指针变量。

    C语言中,如果把数组名作为函数的参数,那么在函数内部就不知道这个数组的元素个数了,需要再增加一个参数来标明这个数组的大小。

     

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    
    // 把数组名作为函数参数
    void arrayNameAsFunctionArgus(int array[10]);
    
    // 将数组内容输出
    void myprint(const int *a, unsigned int n);
    
    // 冒泡排序
    void bubble(int *a, unsigned int n);
    
    void main() {
        
        //int array[10] = {1,2,3,4,5,6,7,8,9,10};
        //arrayNameAsFunctionArgus(array);
    
        int array[3][4] = { { 34, 54, 32, 45 }, { 90, 34, 12, 56 }, {90,220,332,10} };
        bubble(array,sizeof(array) / sizeof(int));
        myprint(array, sizeof(array) / sizeof(int));
    
        system("pause");
    }
    
    // 冒泡排序
    void bubble(int *a,unsigned int n) {
    
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < n - i; j++) {
                if (a[j] < a[j-1]) {
                    int tmp = a[j];
                    a[j] = a[j - 1];
                    a[j - 1] = tmp;
                }
            }
        }
    }
    
    // 将数组内容输出
    void myprint(const int *a, unsigned int n) {
    
        for (int i = 0; i < n; i++) {
            printf("%d
    ",a[i]);
        }
    
    }
    
    
    // 把数组名作为函数参数,当数组名作为函数参数时,C语言将数组名解释为指针(下列三种写法等价)
    //void arrayNameAsFunctionArgus(int array[])
    //void arrayNameAsFunctionArgus(int *array)
    void arrayNameAsFunctionArgus(int array[10]) {
        *array = 100; // 这种写法很危险!!!
        array += 2; // 1.使用者可以修改原始数组里的值,2.若使用者对指针进行运算,还会发生越界!!!正确写法:    func(const int array[])
        // const 保护形参的值不被改变
    
        for (int i = 0; i < 10; i++) {
            printf("%d
    ",array[i]);
        }
    }

    四.memset、memcpy,memmove函数

    4.1 memset函数

    #include <string.h>

    void *memset( void *buffer, int ch, size_t count );

    功能:设置一块内存区域。主要作用:把一块内存重新设置为0

     

    参数说明:第一个参数是内存首地址,第二个参数是要设置的值,第三个参数是这块内存的大小,单位:字节。

    4.2 memcpy函数

    #include <string.h>

    void *memcpy( void *to, const void *from, size_t count );

     

    功能:内存拷贝。使用memcpy的时候要注意内存区域不能重叠。

    参数说明:第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数

    4.3 memmove函数

    #include <string.h>

    void *memmove( void *to, const void *from, size_t count );

    功能:内存移动,参数与memcpy一致。

    参数说明第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数

    示例代码:

    #include <stdio.h> // 这个头文件在系统目录下
    #include <stdlib.h> // 使用了system函数
    #include <string.h> // 使用memset、memcpy、memmove函数
    
    // memset函数的使用
    void memsetDemo();
    
    // memcpy函数的使用(使用memcpy的时候需要注意内存区域不能重叠)
    void memcpyDemo();
    
    // memmove函数的使用
    void memmoveDemo();
    
    void main() {
        
        memmoveDemo();
        system("pause");
    }
    
    // memmove函数的使用
    void memmoveDemo() {
    
        int a1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int a2[10];
        memmove(a2, a1, sizeof(a1));
    
        // 输出a2的值
        for (int i = 0; i < sizeof(a2) / sizeof(int); i++) {
            printf("a2[%d]=%x
    ", i, a2[i]);
        }
    
    }
    
    
    // memcpy函数的使用
    void memcpyDemo() {
        int a1[10] = {1,2,3,4,5,6,7,8,9,10};
        int a2[10];
        memcpy(a2,a1,sizeof(a1));
        
        // 输出a2的值
        for (int i = 0; i < sizeof(a2) / sizeof(int); i++) {
            printf("a2[%d]=%x
    ",i,a2[i]);
        }
    
    }
    
    // memset函数的使用
    void memsetDemo() {
    
        char array[10] = { 0 };
        array[1] = 100;
        array[5] = 2;
        array[8] = 6;
        // 把这块内存的所有成员再次初始化为0
        memset(array,0,sizeof(array));
    
        for (int i = 0; i < sizeof(array) / sizeof(char); i++) {
            printf("array[%d]=%d
    ",i,array[i]);
        }
    }

    五.字符数组与字符串指针

    C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中:

    #include <stdio.h>
    int main(){
        char str[] = "http://www.baidu.com";
        int len = strlen(str), i;
        //直接输出字符串
        printf("%s
    ", str);
        //每次输出一个字符
        for(i=0; i<len; i++){
            printf("%c", str[i]);
        }
        printf("
    ");
        return 0;
    }

    运行结果:
    http://www.baidu.com
    http://www.baidu.com

    或者是定义字符串指针,使用这种方式输出:

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        char *str = "http://www.baidu.com";
        int len = strlen(str), i;
       
    for (i = 0; i < len; i++) { printf("%c", *str++); } printf(" "); return 0; }

    输出结果:
    http://www.baidu.com

    那这两种方式输出的区别在哪呢?区别在于:字符数组存储在全局数据区或者是栈区,而指针指向的字符串存储在常量区。全局数据区或栈区的字符串有读取和写入的权限,而常量区的字符串只有读取的权限,没有写入权限。内存权限不同导致的一个显著的结果就是:字符数组可以在定义后读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后,则不能对其进行修改。

    例:修改字符串中第一个字符为a

    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    
        char *str = "http://www.baidu.com";
        
        int len = strlen(str), i;
        for (i = 0; i < len; i++) {
            printf("%c", *str++);
        }        
        printf("
    ");        
        
        *str = 'a';
            
        return 0;
    }

    运行结果:

    http://www.baidu.com
    Bus error: 10

    出现了段错误。

  • 相关阅读:
    越大优先级越高,优先级越高被OS选中的可能性就越大
    锁标记如果过多,就会出现线程等待其他线程释放锁标记
    使用带缓冲区的输入输出流的速度会大幅提高
    Bufferread有readline()使得字符输入更加方便
    java的开发主要以http为基础
    UDP也需要现有Server端,然后再有Client端
    端口是一种抽象的软件结构,与协议相关
    具有全球唯一性,相对于internet,IP为逻辑地址
    判断是否一个属性或对象可序列化
    把对象通过流序列化到某一个持久性介质称为对象的可持久化
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/6374087.html
Copyright © 2020-2023  润新知