C语言-指针


0.引入 

    int a;      //定义了一个整型变量 名为a 
    a = 100;    // a作为左值, 把100存放到a所对应的存储单元中
    int b = a;  // a作为右值, 取a所对应的存储单元(变量本身)的值, 然后再把这个值存放到变量b对应的空间中


        在C语言中, 任何一个变量名 都有两层含义: 
            左值lvalue : 代表变量所对应的那块地址空间
            右值rvalue : 代表这个变量的值本身(存储单元的值)

        对于变量的访问 也是有两种情况: 
            把一个值保存到这个变量的地址空间中      write 写 
            从一个变量的地址空间中去取值            read 读

        在定义变量的时候, 系统已经把 变量名 和 变量对应的地址 相关联起来了 
            因此 普通的代码中 直接通过变量名 来对变量进行访问 
            但是 系统实际上是通过地址来访问变量的 


            如果我知道了一个变量的地址 
                是不是可以通过该变量的地址 来访问这个变量呢? 
                    可以的 

                如何通过一个对象的地址 来访问这个变量呢? 
                    指针 pointer 


1.变量的访问方式 

    1)直接访问 
        直接通过变量名来访问 

        缺陷: 受作用域的限制

            例子: 
                int func( int a )
                {
                    在func函数中 能否通过变量名a访问到main()函数中的a呢?
                        不能 
                }

                int main()
                {
                    int a = 100;

                    func( a );
                }

    2)间接访问 
        通过对象的地址来访问
            高效, 不受作用域的限制 


2.地址与指针 

    地址: 
        系统把内存 以字节为单位 划分成许多份 进行编号, 这个编号就是内存单元的地址 
                    (无符号32位整数)

        在C语言中, 指针和地址的概念是差不多的, 可以认为指针就是有类型的地址 

            一个对象的首地址, 称之为该对象的 "指针" 
                它标志着该对象的内容是从哪开始的, 我们知道了这个对象的首地址, 就能够访问到该对象的地址空间


3.指针变量 

    指针变量也是一个变量, 它是用来保存一个对象的地址 

        例子: 
                int a = 10;     //a是一个整型变量, 保存的值是一个整数 10 
                
                而指针变量 保存的是地址 


    指针变量的定义语法: 

        数据类型 * 指针变量名; 
        数据类型 * 指针变量名 = 初始值; 

                "数据类型" : 表示指针指向的对象的类型 
                            "指向": 比如 我 指向了 你, 意思就是 我 保存了 你的地址 

                "指针变量名" : 符合C语言合法标识符的定义规则 

                一般建议, 我们定义指针的时候就给它初始化 (或者 说在使用指针之前 一定要给它赋值 ) 
                        要注意赋值符号两边的类型要一致 

            例子: 
                int * p; 

4.如何获取地址? 

    1) 取地址符 & 

            语法: 
                    &对象名         //表示取该对象的地址 (变量,数组元素等)

                例子: 
                    int a = 10;
                    
                    int * p = &a;       //取变量a的地址, 赋值给指针变量p 进行初始化


    2) 有些对象的名字 本身就代表地址 
            数组名 : 数组名就是数组的首地址, 也是数组首个元素的地址 
            函数名
            ...  

        
        注意: 
            1)这两种方法获取到的地址 都是有类型的

            2)在32位操作系统下, 地址都是4个字节的(32位), 即我们分配给指针变量的空间大小都是4个字节(32位) 
                所以 把 指针变量 强制转换成 其他类型的指针变量 它们的值是不会失真 
                    (在64位操作系统下, 指针都是占8个字节)

                例子: 
                    int a = 10;
                    int *p = &a;

                    char * s;
                    s = (char *)p;  

                    printf("p = %p\n", p );
                    printf("s = %p\n", s );

                    printf("sizeof(p) = %ld\n", sizeof(p) );    // 8        在64位操作系统下
                    printf("sizeof(s) = %ld\n", sizeof(s) );    // 8


                            ==================
                                %p : 以十六进制的形式打印指针变量的值

            
    ☆☆☆   3)指针变量的类型只决定 该指针变量的变化 和 实际地址变化 之间的倍率

                例子: 
                    int a = 5;          //假设a的地址为 0x1000 

                    int * p = &a;   
                    char *s = (char *)p; 

                    printf("%p\n", p );         // 0x1000
                    printf("%p\n", p+1 );       // 0x1004
                    printf("%p\n", s );         // 0x1000
                    printf("%p\n", s+1 );       // 0x1001 

                        p+1 : p指向的是int类型的空间, +1之后 指向了下一个int类型的空间
                        s+1 : s指向的是char类型的空间, +1之后 指向下一个char类型的空间 

            总结:      ☆☆☆
                p+i     (p表示指针, i表示整数)    
                        指针和整数做加减运算时, 不是简单的加减数值, 而是加减i个指向单元的长度

                    例子: p和s的定义同上

                            printf("%p\n", p-1 );   // 地址变化 -4
                            printf("%p\n", s-1 );   // 地址变化 -1

                            printf("%p\n", p+3 );   // 地址变化 +12
                            printf("%p\n", s+3 );   // 地址变化 +3

                    注意: 
                        指针和指针相加,是毫无意义的 
                            int *p = &a;
                            int *q = &b;
                            p+q  -->  未知的,无意义的 

5.如何访问 指针 指向的空间呢?   ☆☆☆

    指向符 *   (解引用)

        语法: 
                *地址       //访问该地址对应的那块空间 

                    即 *地址  , 既可以作为左值, 也可以作为右值  
                            左值: 表示地址代表的那块空间 
                            右值: 表示地址所代表的那块空间里面的值 

                例子: 
                        int a = 10;
                        int *p = &a;

                        *p = 5;         // 左值, ==>  a --> 5
                        int b = *p;     // 右值, ==>  b --> 5
                 
            注意: 
                指向符*  和 取地址&  它们之间有什么关系? 
                    由上面的例子 得出  
                        *p = 5;     <==>   a = 5;
                        而 p = &a;

                    ==>  *p = 5;
                    ==>  *(&a) = 5;     <==>  a = 5; 
                    ==>  *(&a) <==> a 

                    * 和 & 可以直接约掉的 


6. 野指针 和 空指针 

    1)野指针 
        是指向一个"未知的"地址的指针 

        例子: 
            int a;      // 变量a的值 是未知的,不确定的 

            int *p ;    // 指针变量p的值也是未知的, 是一个 野指针

                定义一个变量, 系统就会开辟一块内存空间, 不管有没有对它赋值,这个变量都是有值的,只不过这个值是未知的

                如果我们在定义指针变量的时候,没有初始化这个指针,那么它就指向一个未知的地址
                    就是一个野指针 

            使用野指针会有风险的 
                如果 *p  --->  *(未知的地址)  --> 访问一块未知的空间  

                        如果这个地址是你不能访问的(绝大多数情况下 都是不能访问的 )
                        那么运行的时候 就会引发 "段错误" "核心已转储"  (非法访问内存)

                例子: 
                    int *p;     //野指针 
                    *p = 6;     // error 

                    int a;
                    p = &a;     //OK 
                    *p = 6;     //OK   --> a = 6;

                编程建议:
                    野指针在程序中的 危害是很大( 会造成BUG, 而且这个BUG很难找)
                    所以 建议大家 在定义指针的时候 一定要记得初始化(在访问指针之前,一定要给它赋值)

            ================================
                调试代码: 找错误(BUG)
                    1)语法错误  
                        编译的时候,会检查语法错误, 例如: 少了分号, 少量括号, 中文符号 '\346' ... 
                        解决方法: 根据报错提示 找到对应代码的位置 找出错误并修改

                    2)逻辑错误 
                        要运行后,要根据运行的结果才能得出是否有逻辑错误 
                        解决方法: 
                            (2.1)找出错误的位置 
                                打印相关的提示信息 : 一些数据的值(变量), 还可以 打印行号,函数名,文件名 
                                    
                                    //打印行号,函数名,文件名 
                                    printf("line = %d, func = %s, file = %s\n", __LINE__, __FUNCTION__, __FILE__ );

                            (2.2)分析出错的原因并修改

                                    printf("line = %d \n", __LINE__);
                                    A 
                                    printf("line = %d \n", __LINE__);
                                    B 
                                    printf("line = %d \n", __LINE__);
                                    C 
                                    printf("line = %d \n", __LINE__); 
                                    D 
                                    printf("line = %d \n", __LINE__); 
                                    


    2) 空指针 NULL 
        指向不存在的地址(NULL), 就是一个空指针 

            例子: 
                int *p = NULL;      //空指针 
                *p = 6;         //error 

                if( p == NULL )
                {
                    p = &a;
                }

            注意: 
                如果访问空指针, 也会造成"段错误"
                因为 这个NULL是不可访问的, 那么对内存的非法访问,就会造成段错误 

7.一维数组 和 指针 

    数组元素和普通变量是一样的,也是有自己的地址, 并且数组元素之间的地址是连续的

    数组名就是数组的首地址, 即数组首个元素的地址(即a[0]的地址), 是常量, 其值不能被改变  

        例子: 
            1)
                int a[5];
                int *p = &a[0];
                请问有哪些方法 把10赋值给a[0]? 

                    a[0] = 10;
                    *p = 10;    // *(&a[0])  --> a[0]
                    *a = 10;    // *a <==> *(&a[0]) --> a[0]    
                                //数组名a就是数组的首地址, 即数组首个元素的地址(即a[0]的地址)

                    printf("a = %p\n", a );
                    printf("&a[0] = %p\n", &a[0] );

                数组名a 和 数组首个元素a[0]的地址 是一样的 

                可以得出 
                        a <==> &a[0]
                    --> *a <==> *(&a[0])
                    --> *a <==> a[0] 


            2) 表达式 *(a+1) 的含义是什么?  

                    *(a+1)  ==>  *( &a[0] + 1 ) 

                        p+i : 指针和整数做加减运算时, 是加减i个指向单元的长度 
                            且数组元素的地址是连续的, a[0]的下一个是a[1], a[1]的下一个是a[2], ... 

                    因此 
                         *(a+1)  ==> *( &a[0] + 1 ) 
                                 ==> *( &a[1] )
                                 ==> a[1] 

    ☆☆☆  结论: 
                *(a+i) <==> a[i]        (i>=0) 


        练习: 
            1) 
                int a[10];
                int *p = &a[2];
                请问表达式p[2] 表示什么含义? 

                    p[2]  ==> *( p + 2 ) 
                          ==> *( &a[2] + 2 )
                          ==> *( &a[4] )
                          ==> a[4]

            2)  
                char a[3] = {'a', 'b', 'c' };
                printf("%ld %ld %c\n", sizeof(a), sizeof(a+1), *(a+1) );    // 3   4或8   b 

                    sizeof(a)  求整个数组a所占的字节大小? 
                                3 

                    sizeof(a+1) 此时a代表这个数组的首地址 
                                a+1  ==> &a[0] + 1  ==> &a[1] 
                                在32位操作系统下  4 
                                在64位操作系统下  8 

                    *(a+1)  ==>  a[1]  ==> b 


        =====================================
            数组名 在C语言中 有2层含义: 
                (1)代表整个数组 
                    sizeof(a)
                    &a + 1 
                    typeof(a) 

                (2)在合适的情况下,代表指针 数组的首地址, 即数组首个元素的地址 
                    a 
                    a+1 
                    p = a 
                    strlen(a) 

            练习: 
                1) 
                    int a[10] = {0,1,2,3,4,5,6,7,8,9};      //假设a[0]的地址为  0x0000 
                    printf("%p, %p, %p\n", a, a+1, &a+1 );

                        a   代表指针    ==> &a[0]   ==> 0x0000 

                        a+1   a代表指针 &a[0] 
                              a+1 ==> &a[0]+1 ==> &a[1]     ==> 0x0004 

                        &a+1    a代表整个数组 
                                    a的类型是 int [10] 
                                        a[0]的类型是 int 
                                        &a[0]的类型是 int * 
                                    &a的类型是 int [10] * 
                                        &a可以看做是一个指向10个int类型元素的指针 
                                &a+1 就是指向下一个 10个int类型元素的指针, 地址偏移了40 
                                    ==>  0x0028


                2) 
                    &(a+1) 代表什么含义? 

                        &(a+1) ==> &( &a[0] + 1 ) ==> &( &a[1] )  ==> 毫无意义 

                        表达式&a[1]的值是一个无符号的32位的整数(a[1]的地址) 
                            这个整数(地址编号), 没有内存空间来保存的
                            既然没有内存空间, 意味着没有地址, 没有地址就不能取地址 

                

8.二维数组 与 指针 

    int a[3][4];

        二维数组a 可以看做成 有3个元素 每一个元素都是 int [4]类型的 一维数组
                    a[0]  _ _ _ _  
                    a[1]  _ _ _ _ 
                    a[2]  _ _ _ _ 

                因此 
                     *(a+i)  <==>  a[i]     (i>=0)

                     a[i]指的是该一维数组的第i个元素, 同时也可以表示为第i行的首地址, 即a[i][0]的地址  

        ☆☆☆  结论: 
                    *( *(a+i) + j )  <==>  a[i][j]      (i>=0, j>=0)

                        推导: 
                            *( *(a+i) + j )  ==>  *( a[i] + j )
                                             ==>  *( &a[i][0] + j )
                                             ==>  *( &a[i][j] )
                                             ==>  a[i][j]


        二维数组 与 指针 的关系 
            int a[3][4] 

    =========================================================================
        表达式(i>=0, j>=0)                      类型                含义 
            a+i --> &a[0]+i --> &a[i]          int [4] *           第i行的地址

            *(a+i)+j --> &a[i][j]               int *               第i行第j列的那个元素的地址 即&a[i][j] 

            &a[i][j]                            同上 

            *(*(a+i)+j)  --> a[i][j]            int                 第i行第j列的那个元素 


    -------------------------------------------------------------------------------------------------------------
        表达式(i>=0, j>=0)      含义                                            值 
            a                   代表整个数组 
                                    sizeof(a), &a, typeof(a)    
                                代表指针,数组的首地址,即数组首个元素的地址         &a[0]
                                    &a[0]         

            a[0]                代表整个数组 
                                    sizeof(a[0]), typeof(a[0]), &a[0] 
                                代表指针,数组的第一行第一列元素的地址               &a[0][0]
                                    &a[0][0] 

            a[0][0]             数组的第一行第一列元素                              a[0][0]

    
    -----------------------------------------------------------------------------------------------------------------

            a+1                 a代表指针 --> &a[0]+1 --> &a[1]                     &a[1]
                                数组第二行的首地址 
                                typeof(a+1)  --> typeof(&a[1]) --> int [4] *  

            &a[1]               同上 

            &a+1                a代表整个数组 
                                typeof(a) -->  int [3][4]
                                typeof(&a) --> int [3][4] * 
                                &a+1 : 指向下一个数组a类型元素的指针

    -----------------------------------------------------------------------------------------------------------------

            a[1]+2              --> &a[1][0]+2 --> &a[1][2]                     数组第二行第三列的那个元素的地址

            *(a+1)+2            --> a[1]+2  --> &a[1][2]

            &a[1][2]            同上 

    ------------------------------------------------------------------------------------------------------------------

            *(a[1]+2)           --> *( &a[1][0] + 2 )  --> *( &a[1][2] ) --> a[1][2]    数组第二行第三列的那个元素

            *(*(a+1)+2)         --> a[1][2] 

            a[1][2]             同上 
                                    


9.指针数组 和 数组指针 

    数组指针    --> 指针 
                本质是一个指针, 它指向一个数组

    指针数组    --> 数组 
                本质是一个数组, 里面的元素都是指针
    

        1) 数组指针 
            
                int a[4];
                    a的类型是 int [4]

                 
            int (*p) [4];        // int [4] * p  由于 []中括号的优先级 高于 *指向符 

                    p是一个指针, 指向了 int [4]
                    p+1 地址变化了 16 

                        p = &a;

            char (*q)[3];       
                    q是一个数组指针, 指向了 char [3] 
                    q+1 地址变化了 3  
 

        2) 指针数组 
            int * p[4];         // int * (p[4]);  //[]中括号的优先级 高于 *指向符 

                    p是一个数组名, 里面有4个元素,且每一个元素的类型都是 int * 类型 

10.指针变量作为函数的参数 

    在C语言中, 函数参数的传递只有一种情况: 值传递   "传递调用"
                    形参 = 实参的值;    //把实参的值赋值给形参

        例子: 
            //形不改实 
            void exchange( int a, int b )
            {
                int temp = a;
                a = b;
                b = temp;
            }

            int main()
            {
                int a = 5, b = 6;

                exchange( a, b );       //形不改实, 并没有交换实参的值, 实参只是把值传递给了的形参

                printf("a = %d, b = %d\n", a, b );      // 5 6 
            }

        ===> 解决方法 

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

             int main()
            {
                int a = 5, b = 6;
                swap( &a, &b );       
                printf("a = %d, b = %d\n", a, b );      // 6 5 
            }


        1)
            int a[5] = {1,2,3,4,5};
            int *p = (int *)(&a+1);
            printf("%d %d\n", *(a+1), *(p-1) );     // 2 5 

                *(a+1)  ==>  a[1]  ==>  2 

                *(p-1) : 
                        &a+1 :  a代表整个数组 
                                    a的类型是 int [5] 
                                    &a的类型是 int [5] * 
                            &a+1 指向下一个int [5]类型的空间, 地址偏移了20 
                        p = &a+1 , 那么p就执行了这个数组的最后面
                                    a[0]  a[1]  a[2]  a[3]  a[4]
                                    ↑                       ↑     ↑
                                    &a                      ↑     &a+1
                                                            p-1   p 

                    p的类型是 int * 
                        p-1 地址就往前偏移了4 , 即 &a[4] 

                    *(p-1)  ==>  *( &a[4] )  ==>  a[4]  ==>  5 


        2) 
            char b[2][3] = {'1', '2', '3', '4', '5', '\0' };
            printf("%ld, %ld, %s\n", sizeof(b[1]), sizeof(b[1]+1), *(b+1) );

                char b[2][3] 可以看做成一个一维数组, 里面有2个元素 
                                b[0]  _ _ _
                                b[1]  _ _ _    

                    sizeof(b[1])  -->  b[1]代表整个数组, 占 3个字节  -->  3 

                    sizeof(b[1]+1)  -->  sizeof( &b[1][0]+1 ) 
                                    -->  sizeof( &b[1][1] ) 
                                    -->  在32位操作系统下 4个字节 
                                         在64位操作系统下 8个字节 

                    *(b+1)  -->  b[1] 也是第二行的首地址 
                            -->  以s%输出  
                            -->  45

        3)
            int b[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};   //假设b[0][0]的地址为0x0000 
            printf("%p, %p, %p, %p \n", b, b+1, &b[0]+1, &b+1  );

                &b[0][0]  -->  0x0000 

                    b : b代表指针, 数组的首地址, 即数组首个元素的地址 
                        --> 0x0000 

                    b+1 : b代表指针, 数组的首地址, 即数组首个元素的地址
                        --> &b[0] + 1       
                                    b[0]的类型是 int [4]
                                    &b[0]的类型是 int [4] * 
                                    &b[0]+1 指向了下一个int [4]类型的空间, 地址偏移了16  
                        --> 0x0010

                    &b[0]+1 : 同上  -->  0x0010

                    &b+1 : b代表整个数组 
                                b的类型是 int [3][4] 
                                &b的类型是 int [3][4] * 
                                &b+1 指向下一个int [3][4]类型的空间, 地址偏移了48 
                        --> 0x0030 
                            


11.字符串指针 

    1)
        字符    char  

                    char a = '1';       // 49 

        字符串 
            由0个或者多个字符组成的 

            规则: 
                在C语言中,没有字符串类型, C语言中的字符串是通过 char * (字符指针)来实现的

                在C语言中的字符串, 用双引号""引起来的一串字符来表示, 并且字符的后面会默认添加一个'\0' (ASCII码值为0) 
                    这个'\0' 我们称之为字符串的结束符(终止符) 

                我们保存字符串 只需要保存其首地址即可, 可以通过首地址 一个一个的往下找, 直到遇到'\0'为止 
                    期间遇到的所有字符 都是该字符串的内容

                所以 字符串也就是在连续的地址依次存放每一个字符串  

                    例子: 
                        char *p = "12345";
                            编译器在编译时, 会把字符串"12345"的首地址 赋值给指针变量p 

                            注意: 字符串 是保存在内存中 .rodata (只读区域) 中  read - only  

                                typeof('a')  -->  char 
                                typeof("abcd")  -->  const char *       (const代表只读) 


                练习: 
                    1) 
                        char *p = "abcde";
                        *p = 'A';               //error 字符串"abcde"是只读的,不能被修改, 会报错 
                        *(p+1) = 'B';           // error 
                        printf("%s\n", p );

                            p = "12345";    // OK , p是一个指针变量, 它本身是可以改变的, 可以指向其他地方 
                                            // 但是 p指向的内容是字符串,该字符串是不能被修改的

                    2) 
                        char s[] = {"12345"};
                        *(s+2) = 'A';           // OK, *(s+2) ==> s[2] 引用数组元素和引用普通变量是一样的 
                        printf("%s\n", s );     //12A45

                        s[1] = 'B';     //OK 
                        s = "abcde";    // error  , s是数组名, 其值是不能被改变的 

        
        ☆☆☆ 
            总结: 
                    char *p = "hello";      // p是一个指针 
                    char s[] = "hello";     // s是一个数组 

                    指针常量: 
                            数组名s, 是数组的首地址, s的值本身是不能被修改的 
                                    数组s里面的元素(内容)是可以改变的
                            s始终指向同一个位置, 但是s指向的空间里面的内容是可以改变的


                            指针常量 --> 常量 
                                int * const q = &a;    
                                *q = 30;        //OK     p指向的内容是可以改变的    
                                printf("a = %d\n", a );
                                
                                int b;
                                //q = &b;        //error , 但是p本身是不能被改变的     

                        指向常量的指针常量
                            const int c = 10;
                            const int * const r = &c;
                            //r = &a;    // error , r本身是不能被改变的       
                            //*r = 55;    // error , r指向的内容也是不能改变的

                        
                    常量指针: 
                        指针p本身是一个变量, 指针p本身是可以改变的, 
                            指针p可以指向其他地方, 但是 p指向的空间里面的内容是不能被修改的 

                            常量指针  --> 指针  
                                    //const int *p ;
                                    int  const *p = NULL;
                                    int a = 10;
                                    p = &a;            //OK     p本身是可以改变的 
                                    //*p = 20;        //error , 但是p指向的内容是不能改变的
                                    printf("a = %d\n", a );

                记忆点: 看const所修饰的对象是谁,谁就只读(不能被改变)                


    2) 关于字符串相关的处理函数 

        (1)strlen() 

                NAME
                    strlen - calculate the length of a string
                SYNOPSIS
                    #include <string.h>

                    size_t strlen(const char *s);   
                        功能: 求字符串的长度, 直到遇到'\0', 且不计算'\0'
                        参数: 
                            s : 所求的字符串的首地址 
                        返回值: 
                            返回该字符串的长度 

                练习: 
                    设计一个函数, 实现 strlen()的功能 

                        int my_strlen( char *s )
                        {
                            if( s == NULL )
                            {
                                return 0;
                            }
                            
                            /*
                                //指针 
                                int len = 0;
                                while( *s != '\0' )
                                {
                                    len++;
                                    s++;
                                }
                                return len;
                            */

                            //数组 
                            int len = 0;
                            int i = 0;
                            while( s[i] != '\0' )    // *(s+i) != '\0'
                            {
                                len++;
                                i++;
                            }
                            return len;
                        }  


        (2) strcpy() 

                NAME
                    strcpy, strncpy - copy a string
                SYNOPSIS
                    #include <string.h>

                    char *strcpy(char *dest, const char *src);  // '\0'也会复制 

                    char *strncpy(char *dest, const char *src, size_t n);

                        功能: 把src字符串 拷贝到 dest指向的空间中 
                        参数: 
                            dest: 目标地址 
                            src: 源字符串 
                            n : 指定要复制的字符个数 
                        返回值: 
                            将dest字符串返回

                练习: 


                    写一个函数,实现strcpy()的功能 

                        char * my_strcpy( char *dest, char *src )
                        {
                            /*
                                //指针 
                                char *p = dest;
                                while( *src != '\0' )
                                {
                                    *p = *src;
                                    p++;
                                    src++;
                                }
                                *p = '\0';
                                return dest;
                            */

                            //数组 
                            int i=0;
                            /*
                                while( src[i] != '\0' )
                                {    
                                    dest[i] = src[i];
                                    i++;
                                }
                                dest[i] = '\0';
                            */
                            while( dest[i] = src[i] )    //赋值表达式
                            {
                                i++;
                            }
                            return dest;
                        }

                        char * my_strncpy(char *dest, char *src, int n)
                        {
                            int i;
                            for( i=0; i<n && src[i] != '\0'; i++ )
                            {
                                dest[i] = src[i];
                            }
                            for( ; i<n; i++ )   //n大于src的长度, 剩下的全部复制为\0
                            {
                                dest[i] = '\0';
                            }
                            return dest;
                        }

        (3) strcmp() 
                NAME
                    strcmp, strncmp - compare two strings
                SYNOPSIS
                    #include <string.h>

                    int strcmp(const char *s1, const char *s2);

                    int strncmp(const char *s1, const char *s2, size_t n);

                        功能: 比较两个字符串 
                        参数: 
                            s1 s2 : 两个字符串 
                            n : 指定要比较的字符个数
                        返回值: 
                            0  表示两个字符串相等 
                            非0 不相等 返回不相等的那两个字符差值 ( *s1 - *s2 )
                                >0   s1 > s2  
                                <0   s1 < s2 

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

int my_strcmp( char *s1, char *s2)
{
    //数组 
    int i;
    for( i=0; s1[i] == s2[i] ; i++ )
    {
        if( s1[i] == '\0' )
        {
            return 0;
        }
    }
    return s1[i] - s2[i];

    /*
        //指针 
        for( ; *s1 == *s2 ; s1++, s2++ )
        {
            if( *s1 == '\0' )
            {
                return 0;
            }
        }
        return *s1 - *s2;
    */
}

int main()
{
	char *s1 = "abcdefg";
	char *s2 = "abcdabc";
	
	printf("%d\n", strcmp(s1, s1 ) );	// 0
	printf("%d\n", strcmp(s1, s2 ) ); 	// 4 
	printf("%d\n", strcmp(s2, s1 ) );	// -4
	printf("%d\n", strncmp(s1, s2 , 4) );


	//判断后缀名
	char *p = "01.mp3";
	char *q = "0123456.mp3";
	if( strcmp( q + ( strlen(q)-4 ) , ".mp3" ) == 0 )
	{
		printf("Yes \n\n");
	}

	//自定义接口 
	printf("%d\n", my_strcmp(s1, s1 ) );	// 0
	printf("%d\n", my_strcmp(s1, s2 ) ); 	// 4 
	printf("%d\n", my_strcmp(s2, s1 ) );	// -4
	
}

        (4) strcat()  

                NAME
                    strcat, strncat - concatenate two strings

                SYNOPSIS
                    #include <string.h>

                    char *strcat(char *dest, const char *src);

                    char *strncat(char *dest, const char *src, size_t n);

                        功能: 把src字符串 拼接到 dest字符串的末尾  
                        参数: 
                            dest : 目标字符串 
                            src : 源字符串 
                            n : 指定要拼接的字符个数
                        返回值: 
                            返回dest字符串 

                    例子: 
                        char * s1 = "abcdefg";
                        char s2[32] = {"123456789"};

                        strcat( s2, s1 );
                        printf("s2 = %s\n", s2 );   //123456789abcdefg

                        char s3[32] = {"zxcvbnm"};
                        strncat( s3, s1, 5 );
                        printf("s3 = %s\n", s3 );   //zxcvbnmabcde


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

char * my_strcat(char *dest, char *src)
{
    char *p = dest;     //保存字符串的首地址
    while( *dest )      //让dest往后走到末尾位置 即'\0'
    {
        dest++;
    }

    while( *dest = *src )
    {
        dest++;
        src++;
    }
    return p;   
}

int main()
{
	char * s1 = "abcdefg";
	char s2[32] = {"123456789"};

	strcat( s2, s1 );
	printf("s2 = %s\n", s2 );

	char s3[32] = {"zxcvbnm"};
	strncat( s3, s1, 5 );
	printf("s3 = %s\n", s3 );


	char s4[] = "123abc456efg789456";
	char s5[] = "456";

	char *p = strstr( s4, s5 );
	if( p != NULL )
	{
		printf("p = %s\n", p );
	}
	else 
	{
		printf("No \n");
	}

	//自定义的接口 

	char s6[32] = {"12345"};
	my_strcat( s6, s1 );
	printf("\ns6 = %s\n", s6 );
}


        (5) strstr() 
                NAME
                    strstr - locate a substring

                SYNOPSIS
                    #include <string.h>

                    char *strstr(const char *haystack, const char *needle);
                        功能: 定位子字符串的位置 (判断needle是否为haystack的子字符串)
                        参数: 
                            haystack , needle , 两个字符串 
                        返回值: 
                            成功, 返回 子字符串所在的位置(地址) (所找到的第一个子字符串)
                            失败, 返回NULL

                    例子: 
                        char s4[] = "123abc456efg789456";
                        char s5[] = "456";

                        char *p = strstr( s4, s5 );
                        if( p != NULL )
                        {
                            printf("p = %s\n", p );     //456efg789456
                        }
                        else 
                        {
                            printf("No \n");
                        }

12.函数指针 和 指针函数 

    1)函数指针 
        在C语言中, 不仅变量有地址, 数组有地址, 函数也是有地址的 
            用一个指针变量来保存函数的地址, 那么这个指针该怎么去定义? 

        (1)定义语法 
                指针的定义语法: 
                    指向的类型 * 指针变量名; 

                例子: 
                    int sum( int a, int b )
                    {
                        return a+b;
                    }

                    函数的类型: 
                        typeof( sum )  -->  int (int, int) 
                                            意味着 这个函数返回值为int类型,且带有两个int类型的参数 

                那么, 定义一个指针变量来保存这个函数的地址 该如何定义呢? 

                    int (int, int) * p;
                ==> 
                    int (*p) (int,int);     //p就是一个函数指针,
                                            //指向的这个函数是 有一个int类型的返回值,且带有两个int类型参数的函数

        (2)初始化函数指针 

            p = 函数的地址;

            -->     p = &函数名;    或者    p = 函数名;

                例如: 
                    p = &sum;   或者  p = sum; 


        (3)如何通过函数指针来调用函数? 

                平时调用函数: 
                    函数名(实参);       ==>  sum( 1, 2);

                通过函数指针来调用函数: 
                    函数指针(实参);     ==>  p( 1 ,2 );         // p = sum;
                    (*函数指针)(实参)   ==>  (*p)( 1,2 );       // p = &sum;

                例子: 
                    int sum( int a, int b )
                    {
                        return a+b;
                    }

                    int main()
                    {
                        int (*p) (int, int);
                        int (*q) (int, int);

                        p = sum;    // ok
                        q = &sum;    // ok

                        printf("sum = %d\n", sum(1,2) );    //函数名 直接调用

                        //通过函数指针来调用函数 
                        printf("p = %d\n", p(1,2) );
                        printf("*q = %d\n", (*q)(1,2) );
                    }

        (4)函数指针的应用 
            a)线程池 (二阶段-并发)
            b)回调函数 

            ==========================================
            回调函数: 
                就是一个被作为参数传递的函数 

                可以把一个函数的指针 作为参数 传递给另外一个函数 
                以便于该函数在处理相应的事件时, 可以灵活的使用不同的方法 

                示例: 
                    一般的操作系统都实现了中断机制   // 中断: 打断CPU指令执行顺序的事件 
                    但是 用户不能直接定义中断处理函数的 
                    只能在中断处理函数里面 再调用 用户自定义的回调函数 

                例子: 
                    #include <stdio.h>

                    void (*call_back)(void);    //函数指针

                    void exit_isr(void)        //外部中断处理函数, 由操作系统实现
                    {
                        //... 

                        call_back();

                        //...
                    }

                    void abc(void)    //用户自定义的处理函数1
                    {
                        printf("hello world\n");
                    }

                    void xyz(void)  //用户自定义的处理函数2 
                    {
                        printf("hahahahah\n");
                    }

                    int main()
                    {
                        call_back = abc;
                        exit_isr();

                        call_back = xyz;
                        exit_isr();
                    }

        
        (5)函数指针数组 

            int (*p[10]) (int,int);     
                        //定义了一个函数指针数组, 数组名为p, 里面有10个元素,且每一个元素都是函数指针 
                        // 指向了一个返回值为int,且带有两个int类型参数的函数

                    p[0]的类型  int (int, int) * 
                    p[1] ... 


    2) 指针函数  

        int * p (int , int );       //指针函数  (返回值为指针类型的普通的函数)

        语法: 
            指针函数的返回值类型 * 指针函数名(参数列表);

13.main函数带参数 

    语法: 
        int main( int argc, char *argv[] )
        {
        }
            argc : 是一个int类型, 代表命令行参数的个数 ( ./a.out也算一个)

            argv : 是一个字符串数组 
                    argv[0] 保存的就是程序的第一个参数 , 即 ./xxx (例如: ./a.out )
                    argv[1] 保存的是程序的第二个参数, 即 你实际输入的第一个参数 
                    argv[2]
                    ...

                argv[0]保存的是 启动该程序的程序名, 比如: ./a.out 
                    因此 argc的值至少为1 


            例子: 
                int main( int argc, char *argv[] )
                {
                    printf("argc = %d\n", argc );

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

                    运行:  ./a.out  abc  123  xyz  

                        argc --> 4 

                        argv[0] -- ./a.out 
                        argv[1] -- abc 
                        argv[2] -- 123 
                        argv[3] -- xyz 


            练习: 
                实现求两个整数之和, 参数从命令行输入 

                    int main( int argc, char *argv[] )
                    {
                        int a = atoi( argv[1] );
                        int b = atoi( argv[2] );
                        int s = a+b;
                        printf("sum = %d\n", s );
                    }

                    ./a.out  159 341 

            
            ==============================
                atoi()  把数字字符串 转换成 一个整数 

                    NAME
                        atoi - convert a string to an integer
                    SYNOPSIS
                        #include <stdlib.h>
                        int atoi(const char *nptr);
                            功能: 把数字字符串 转换成 一个整数 
                            参数: 
                                nptr: 指定要转换的数字字符串 
                            返回值: 
                                返回该数字字符串所对应的整数 
                        
                        例如: 
                            int a = atoi( "123" );      // a = 123 

14.二级指针 和 多级指针 

    int a = 5;
    int *p = &a;    // p是一个指针变量, 保存变量a的地址 
                    // p是一个 一级指针 

        对于 指针变量p本身来说, 也是有一个内存空间来保存, 那么 是不是也可以定义一个指针来保存 指针变量p的地址呢? 

            typeof(p)  -->  int * 

            指针的定义语法: 
                指向的类型 * 指针变量名; 

                -->     typeof(p) * q;
                -->     int * * q;
                                    // q就是一个二级指针, 它保存一个 一级指针的地址  
                
                ==>  q = &p;        //q指向了p

                        *q  ==>  *(&p)  ==> p  ==>  &a 

                        **q  ==> **(&p)  ==> *p  ==> *(&a)  ==> a  

                        **q = 10;   ==>  a = 10;


        那么 对于二级指针q本身也是有地址, 我们是不是可以再定义一个指针 保存这个 二级指针q的地址呢? 

                typeof(q)  --->  int ** 
                ==> 
                    int ***r;       //三级指针 
                    r = &q;

            ....

            多级指针  


        练习: 
            int a[2][3] = {1,2,3,4,5,6};
            请问 表达式 **a 的含义? 

                **a  -->  **( &a[0] ) 
                     -->  *( a[0] )
                     -->  *( &a[0][0] )
                     -->  a[0][0]
                     -->  1 

15.动态内存的分配函数 

    malloc  /  realloc  / calloc      / free()
    是从"堆空间"去分配内存的                释放空间

        堆heap 
            堆空间, 生存期是随进程的持续性而一直存在的
                从堆上分配的内存,一旦分配成功, 就会一直存在, 直到你手动free释放或者进程结束 
                 

            NAME
                malloc, free, calloc, realloc - allocate and free dynamic memory

            SYNOPSIS
                #include <stdlib.h>

                void *malloc(size_t size);
                void free(void *ptr);
                void *calloc(size_t nmemb, size_t size);
                void *realloc(void *ptr, size_t size);

    1) malloc 

        void *malloc(size_t size); 
            功能: 用来分配一块size大小的动态内存空间 (未初始化的)
            参数: 
                size: 指定需要分配的内存空间的大小, 单位是 字节
            返回值: 
                void * 通用指针 
                成功,返回分配到的内存空间的首地址 
                失败,返回NULL 

            注意: 
                malloc是从堆上面分配的内存空间,这块空间的生存期是 随进程的持续性
                    这个空间不会自动回收, 必须要用free手动释放 回收资源 


    2) calloc 

        void *calloc(size_t nmemb, size_t size);    
            功能: 作用和malloc类似, 也是从堆上面分配一段地址连续的内存空间,只不过它是给数组分配的
            参数: 
                nmemb: 表示你要分配多少个元素
                size : 每个元素占多少个字节的空间
            返回值: 
                void * 通用指针 
                成功,返回分配到的内存空间的首地址 
                失败,返回NULL

            注意: 
                calloc( n, size )   <==>  malloc( n*size );
                但是 calloc分配的空间 它会把这个空间的内容全部清0, 而malloc就不会

        
    3) realloc 

        void *realloc(void *ptr, size_t size);
            功能:把ptr指向的空间, 扩展成size大小 
                    ptr指向的空间 必须是malloc/calloc/realloc分配的空间的地址
            参数: 
                ptr: 指向一个堆空间的地址, 是你需要扩张的首地址
                size: 重新扩张为size大小
            返回值: 
                void *通用指针
                成功,返回 扩张后的内存空间的首地址
                失败,返回 NULL 

            注意: 
                (1) 如果 ptr == NULL 
                        realloc( NULL, size )   ==>  malloc( size )

                (2) 如果 ptr != NULL 

                        size > 原来的大小 , realloc用来把ptr指向的空间, 扩张到size大小的空间
                                            原来前面的内容保持不变, 后面新增的空间的内容也不会初始化

                        size == 0   --->    realloc( ptr, 0 )  --> free( ptr )

                        size < 原来的大小  ,这种情况是未定义的(什么后果都有可能)
 

    4) free 

        void free(void *ptr);
            功能: 释放ptr指向的堆空间, 必须是malloc/calloc/realloc分配的空间的地址
            参数: 
                ptr: 指向你需要释放的堆空间的首地址
            返回值: 
                无 

            注意: 
                malloc/realloc/calloc分配的是堆空间,一旦分配成功就一直存在,直到你手动free释放或者进程结束 
                如果你没有收到释放掉, 那么操作系统也不会把它分配给别人了 
                最后会造成内存空间越来越小 "内存泄漏" "垃圾内存"

        例子: 
            1)
                把以下的代码, 改写成动态分配的内存 
                    int n;
                    scanf("%d", &n );
                    int a[n];
                ------------------------
                ==> 
                    int n;
                    scanf("%d", &n );

                    //int *a = (int *)malloc( n*sizeof(int) );
                    int *a = (int *)calloc( n, sizeof(int) );
                    if( a == NULL )
                    {
                        printf("malloc error \n");
                        return ;
                    }

                改写完之后, 再尝试 从键盘上获取数组元素的值, 并打印输出

                    int i;
                    for( i=0; i<n; i++ )
                    {
                        scanf("%d", &a[i] );
                    }

                    for( i=0; i<n; i++ )
                    {
                        printf("%d ", a[i] );
                    }
                    putchar('\n');

                //重新扩张
                    a = realloc( a, n*sizeof(int) + 8 );
                    a[i] = 6;
                    a[i+1] = 7;

                    for( i=0; i<(n*sizeof(int)+8)/sizeof(int); i++ )
                    {
                        printf("%d ", a[i] );
                    }
                    putchar('\n');
                                

            2) 小明 自认为是一个非常🐂🍺的程序员, 它写了如下的代码: 
                
                    void func()
                    {
                        char *p = malloc( 100 );
                        p = "12345";
                        printf("p = %s\n", p );
                    }
                    请评价以上的代码: 

                        malloc动态分配的100个字节的快捷, 只能通过指针变量p去访问这块空间
                        但是 又把字符串12345的地址赋值给p, p转而去指向这个字符串了
                        那么 新增的这个动态分配的100个字节的空间就无法访问(没有指针指向它了,找不到它了)
                        而操作系统也不能分配给别人, 最后造成了垃圾内存 


    ==============================
        NAME
            memset - fill memory with a constant byte

        SYNOPSIS
            #include <string.h>

            void *memset(void *s, int c, size_t n);
                功能: 把s指向的空间的n个字节 全部设置为c值 
                参数: 
                    s: 指向需要被设置的内存空间的首地址
                    c: 指定的内容 
                    n: 指定的字节数 
                返回值: 
                    返回被设置之后的空间的首地址(s)

            例子: 
                char * p = malloc(10);  //malloc分配的未初始化的堆空间
                int i;
                for( i=0; i<10; i++ )
                {
                    printf("%d ", p[i] );
                }
                putchar('\n');

                //memset( p, 0, 10 );
                memset( p, 1, 10 );
                for( i=0; i<10; i++ )
                {
                    printf("%d ", p[i] );
                }
                putchar('\n');

数据治理是确保数据准确性、可靠性、安全性、可用性和完整性的体系和框架。它定义了组织内部如何使用、存储、保护和共享数据的规则和流程。数据治理的重要性随着数字化转型的加速而日益凸显,它能够提高决策效率、增强业务竞争力、降低风险,并促进业务创新。有效的数据治理体系可以确保数据在采集、存储、处理、共享和保护等环节的合规性和有效性。 数据质量管理是数据治理中的关键环节,它涉及数据质量评估、数据清洗、标准化和监控。高质量的数据能够提升业务决策的准确性,优化业务流程,并挖掘潜在的商业价值。随着大数据和人工智能技术的发展,数据质量管理在确保数据准确性和可靠性方面的作用愈发重要。企业需要建立完善的数据质量管理和校验机制,并通过数据清洗和标准化提高数据质量。 数据安全与隐私保护是数据治理中的另一个重要领域。随着数据量的快速增长和互联网技术的迅速发展,数据安全与隐私保护面临前所未有的挑战。企业需要加强数据安全与隐私保护的法律法规和技术手段,采用数据加密、脱敏和备份恢复等技术手段,以及加强培训和教育,提高安全意识和技能水平。 数据流程管理与监控是确保数据质量、提高数据利用率、保护数据安全的重要环节。有效的数据流程管理可以确保数据流程的合规性和高效性,而实时监控则有助于及时发现并解决潜在问题。企业需要设计合理的数据流程架构,制定详细的数据管理流程规范,并运用数据审计和可视化技术手段进行监控。 数据资产管理是将数据视为组织的重要资产,通过有效的管理和利用,为组织带来经济价值。数据资产管理涵盖数据的整个生命周期,包括数据的创建、存储、处理、共享、使用和保护。它面临的挑战包括数据量的快速增长、数据类型的多样化和数据更新的迅速性。组织需要建立完善的数据管理体系,提高数据处理和分析能力,以应对这些挑战。同时,数据资产的分类与评估、共享与使用规范也是数据资产管理的重要组成部分,需要制定合理的标准和规范,确保数据共享的安全性和隐私保护,以及建立合理的利益分配和权益保障机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值