• C Primer Plus之指针


       c之精髓——指针(pointer)——用来存储地址的变量。一般来讲,指针是一个其数值为地址的变量(或更一般地说是一个数据对象)。

       一元运算符&可以取得变量的存储地址,一个变量的地址可以被看作是该变量在内存中的位置。

       地址运算符&:后跟一个变量名时,&给出该变量的地址。

       间接运算符*:当后跟一个指针名或地址时,*给出存储在被指向地址中的数值。

       指针声明,举例如下:

    int * pi;
    char * pc; // pc所指向的值(*pc)是char类型的,而pc本身又是什么类型?我们把它描述为“指向char的指针”类型。指针是一种新的数据类型
    float * pf, * pg; 

       类型标识符表明了被指向变量的类型,而星号(*)表示该变量为一指针。声明int * pi;的意思是pi是一个指针,而且*pi是int类型的。

      

       数组名同时也是该数组首元素的地址。也就是说,如果flizhy是一个数组,那么flizhy == &flizhy[0]; flizhy和&flizhy[0]都代表首元素的内存地址。

    1. 指针的数值就是它所指向的对象的地址。地址的内部表示方式是硬件来决定的。很多计算机(包括PC机和Macintosh机)都是以字节编址的,这意味着对每个内存字节顺序进行编号,对于包含多个字节的数据类型,比如double类型的变量,对象的地址通常指的是其首字节的地址
    2. 在指针前运用运算符*就可以得到该指针所指向的对象的数值
    3. 对指针加1,等价于对指针的值加上它所指向的对象的字节大小

       数组和指针之间的密切关系:可以用指针标识数组的每个元素,并得到每个元素的数值。从本质上来说,对同一个对象有两种不同的符号表示方法。c语言标准在描述数组时,确实借助了指针的概念。例如:定义ar[n]时,意思是*(ar + n),即“寻址到内存中的ar,然后移动n个单位,再取出数值”。

       

       在函数原型或函数定义头的场合中(并且也只有在这两种场合中),可以用int * ar代替int ar[]

    int sum(int * ar, int n); 
    int sum(int ar[], int n);

        c提供了8种基本的指针操作:(了解就好)

    1. 赋值——可以把一个地址赋给指针。通常使用数组名或地址运算符&来进行地址赋值。(注意:地址应该和指针类型兼容。也就是说,不能把一个double类型的地址赋给一个指向int的指针。c99允许使用类型指派这样做,但是我们不推荐使用这种方法。)
    2. 求值或取值——运算符*可取出指针指向地址中存储的数值。
    3. 取指针地址——指针变量同其他变量一样具有地址和数值,使用运算符&可以得到存储指针本身的地址。
    4. 将一个整数加给指针——可以使用+运算符把一个整数加给一个指针,或者把一个指针加给一个整数。两种情况下,这个整数都会和指针所指类型的字节数相乘,然后所得到的结果会加到初始地址上。如果相加的结果超出了初始指针所指向的数组的范围,那么这个结果是不确定的,除非超出数组最后一个元素的地址能够确保是有效的。
    5. 增加指针的值——可以通过一般的加法或增量运算符来增加一个指针的值。对指向某数组元素的指针做增量运算,可以让指针指向该数组的下一个元素。
    6. 从指针中减去一个整数——可以使用-运算符来从一个指针中减去一个整数。指针必须是第一个操作数,或者是一个指向整数的指针。这个整数都会和指针所指类型的字节数相乘,然后所得的结果会从初始地址中减掉。(同4)
    7. 减小指针的值——指针当然也可以做减量运算。
    8. 求差值——可以求出两个指针间的差值。通常对分别指向同一个数组中两个元素的指针求差值,以求出元素之间的距离。差值的单位是相应类型的大小。有效指针差值运算的前提是参加运算的两个指针是指向同一个数组(或是其中之一指向数组后面的第一个地址)。指向两个不同数组的指针之间的差值运算可能会得到一个数值结果,但也可能会导致一个运行时错误。
    9. 比较——可以运用关系运算符来比较两个指针的值,前提是两个指针具有相同的类型。

       ¤使用指针,有一个规则需要特别注意:不能对未初始化的指针取值!!!

       例:

    int * pt;  // 未初始化的指针
    * pt = 5; // 一个可怕的错误!!! 表示把数值5存储在pt所指向的地址中。但是由于pt没有被初始化,因此它的值是随机的,不知道5会被存储在什么位置。这个位置也许对系统危害不大,但也许会覆盖程序数据或代码,导致程序的崩溃。

       切记:当创建一个指针时,系统只分配了用来存储指针本身的内存空间,并不分配用来存储数据的内存空间。因此在使用指针之前,必须给它赋予一个已分配的内存地址。再如:

    char * name;
    scanf("%s", name);

       这可能会通过编译器,但是在读入name的时候,name会覆盖程序中的数据和代码,并可能导致程序异常终止。这是因为scanf()把信息复制到由参数给定的地址中,而在这种情况下,参数是个未初始化的指针,name可能指向任何地方。再如:

    char * str;
    strcpy(str, "The C of Tranquility");

       函数将把字符串“The C of Tranquility”复制到str指定的地址中,但是str没有初始化,因此这个字符串可能被复制到任何地方!

       结构中使用字符数组还是字符指针?

       例如:

    #define LEN 20
    struct names {
        char first[LEN];
        char last[LEN];
    };

       可以改写成下面这样吗?

    struct pnames {
        char * first;
        char * last;
    }

       对于struct names结构类型的变量来说,字符串存储在结构内部,这个结构总共分配了40字节来存放两个字符串。然而对于struct pnames结构类型的变量来说,字符串存储在编译器存储字符串常量的任何地方。这个结构存放的只是两个地址而已。struct pnames结构不为字符串分配任何存储空间。它只适用于在其他的地方已经为字符串分配了空间(例如字符串常量或数组中的字符串)。简单地说,pnames结构中的指针应该只用来管理那些已创建的而且在程序其他地方已经分配过空间的字符串

       所以,对于以下程序:

    struct pnames attorney;
    puts("Enter the last name of your attorney: ");
    scanf("%s", attorney.last);

       scanf()把字符串放到由attorney.last给出的地址中。因为这是个未经初始化的变量,所以该地址可能是任何值,程序就可以把名字放在任何地方。

       因此,如果需要一个结构来存储字符串,请使用字符数组成员。存储字符指针有它的用处,但也有被严重误用的可能。

       如果非得使用指针处理字符串,也是可以的,可借助malloc()函数分配内存,并使用指针来存放地址。这个方法的优点是可以请求malloc()分配刚好满足字符串需求数量的空间。

       例如:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct namect {
        char * fname;
        char * lname;
        int letters;
    };
    void getinfo(struct namect *);
    void makeinfo(struct namect *);
    void showinfo(const struct namect *);
    void cleanup(struct namect *);
    
    int main(void)
    {
        struct namect person;
    
        getinfo(&person);
        makeinfo(&person);
        showinfo(&person);
        cleanup(&person);
        return 0;
    }
    void getinfo(struct namect * pst)
    {
        char temp[81];
        printf("Please enter your first name.
    ");
        gets(temp);
        pst->fname = (char *)malloc(strlen(temp) + 1);
        strcpy(pst->fname, temp);
        printf("Please enter your last name.
    ");
        gets(temp);
        pst->lname = (char *)malloc(strlen(temp) + 1);
        strcpy(pst->lname, temp);
    }
    void makeinfo(struct namect * pst)
    {
        pst->letters = strlen(pst->fname) + strlen(pst->lname);
    }
    void showinfo(const struct namect * pst)
    {
        printf("%s %s, your name contains %d letters.
    ", pst->fname, pst->lname, pst->letters);
    }
    void cleanup(struct namect * pst)
    {
        free(pst->fname);
        free(pst->lname);
    }
    View Code

       有关const

       1° 使用关键字const保护数组

    #define MONTHS 12
    ...
    const int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

       如果随后的程序代码试图改变数组,将得到一个编译时的错误:

    days[9] = 44; // 编译时错误

       2° 指向常量的指针不能用于修改数值

    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    const double * pd = rates; // 把pd声明为const double的指针,这样就不可以使用pd来修改它所指向的数值
    * pd = 29.89; // 不允许
    pd[2] = 222.22; // 不允许
    rates[0] = 99.99; // 允许,因为rates并没有声明为常量,所以仍可以使用rates来修改其数值
    pd++; // 让pd指向rates[1],这是允许的

       结论:通常把指向常量的指针用作函数参量,以表明函数不会用这个指针来修改数据。例:

    void show_array(const double * ar, int n);

       3° 将常量或非常量数据的地址赋给指向常量的指针是合法的。例:

    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
    const double * pc = rates;  // 合法
    pc = locked; // 合法
    pc = &rates[3]; // 合法

       然而,只有非常量数据的地址才可以赋给普通的指针

    double * pnc = rates; // 合法
    pnc = locked; // 非法
    pnc = &rates[3]; // 合法

       4° 使用关键字const来声明并初始化指针,可以保证指针不会指向别处

    double * const pc = rates; // 这样的指针仍然可以用于修改数据,但他只能指向最初赋给他的地址

       5° 可以使用两个const来创建指针,这个指针既不可以更新所指向的地址,也不可以修改所指向的数据

    const double * const pc = rates;

       6° 还有一种放置const关键字的方法:

    float const * pfc; // 等同于:const float * pfc;

       总而言之:一个位于*左边任意位置的const使得数据成为常量,而一个位于*右边的const使得指针本身成为常量。

       const和static

       在文件中之中共享const数据时要小心,以下是其中策略之一:

       将常量放在一个include文件中,这时还必须使用静态外部存储类:

    /* constant.h--定义一些全局变量 */
    static const double PI = 3.14159; 
    const static double pi = 3,14;       // const/static 顺序无关紧要,看上去是这样的!!!
    static const char * MONTHS[12] = {
        "January", "February", "March", "April", "May", "June", "July",
        "August", "September", "October", "November", "December"};

       ↓ 使用"constant.h"

    /* file1.c--使用在其他文件中定义的全局变量 */
    #include "constant.h"
    
    /* file2.c--使用在其他文件中定义的全局变量 */
    #include "constant.h"

       主要是为了说明const、static的顺序是无关紧要的!!!

       数组变量和指针之间的关系

       question:

       数组变量可以被看作是const的指针变量,到底是“可以被看作”,还是“就是”指针呢?用程序证据(而不是搜索教科书)来说明你的观点。(提示:如果对const指针的所有的操作都可以对数组变量做,而且结果一致,就说明数组变量就是指针;如果有某个操作不能做,或者结果不一致,就说明不是指针)

       answer:

       数组变量可以看做指针但是不是指针,如下计算数组长度和指针长度时,两者值不一样。

    #include <stdio.h>
    
    int main(void)
    {
        int marbles[10] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20};
        int * const p = marbles;
    
        printf("%lu
    ", sizeof(marbles));  // 40
        printf("%lu
    ", sizeof(p));        // 4
        return 0;
    }

       指针和多维数组

       以int zippo[4][2]为例进行说明

       1° zippo[0]是一个整数大小对象的地址,而zippo是两个整数大小对象的地址。因为整数和两个整数组成的数组开始于同一个地址,因此zippo和zippo[0]具有相同的数值。

       2° zippo所指向对象的大小是两个int,而zippo[0]所指向对象的大小是一个int。因此zippo+1和zippo[0]+1的结果不同。

       3° 因为zippo[0]是其首元素zippo[0][0]的地址,所以*(zippo[0])代表存储在zippo[0][0]中的数值,即一个int数值。同样,*zippo代表其首元素zippo[0]的值,但是zippo[0]本身就是一个int数值的地址,即&zippo[0][0],因此,*zippo是&zippo[0][0]。对这两个表达式同时应用取值运算符将得到**zippo等价于*&zippo[0][0],后者简化后即为一个int数zippo[0][0]。简言之,zippo是地址的地址,需要两次取值才可以得到通常的数值。地址的地址或指针的指针是双重间接的典型例子。

       关于第3点,我的理解:

       zippo[0]为一指针→*(zippo[0])= zippo[0][0]

       *zippo的值为代表其首元素zippo[0](一个一维数组)地址,即*zippo仍然为一指针→*(*zippo) = *(zippo[0]) = zippo[0][0]

       分析*(*(zippo + 2)+ 1)

    zippo 第1个大小为2个int的元素的地址
    zippo+2 第3个大小为2个int的元素的地址
    *(zippo+2) 第3个元素,即包含2个int值的数组,因此也是其第1个元素(int值)的地址
    *(zippo+2)+ 1 包含2个int值的数组的第2个元素(int值)的地址
    *(*(zippo+2)+ 1) 数组第3行第2个int(zippo[2][1])的值

       更一般地,要表示单个元素,可以使用数组符号或指针符号;并且在这两种表示中即可以使用数组名,也可以使用指针:

    zippo[m][n] = *(*(zippo +m) + n);

       奇特的声明

       当进行一个声明时,可以添加一个修饰符来修饰名称(或标识符)。

       声明时可以使用的修饰符

    修饰符 含义
    * 表示一个指针
    () 表示一个函数
    [] 表示一个数组

       C允许同时使用多于1个的修饰符。

       弄清楚这些声明的诀窍便于理解使用修饰符的顺序:

    1. 表示一个数组的[]和表示一个函数的()具有同样的优先级,这个优先级高于间接运算符*的优先级。这意味着下面的声明使得risks是一个指针数组,而不是一个指向数组的指针:
      int * risks[10];
    2. []和()都是从左到右进行结合的。下面的声明使goods是一个由12个具有50个int值的数组构成的数组,而不是一个由50个具有12个int值的数组构成的数组。
      int goods[12][50];
    3. []和()具有相同的优先级,但由于它们是从左到右结合的,所以下面的声明在应用[]之前先将*和rusks组合字一起。这意味着rusks是一个指向具有10个int值的数组的指针:
      int (* rusks)[10];
    int * oof[3][4];

       [3]具有比*更高的优先级,并且根据从左到右的规则,它的优先级比[4]高。因此,oof是一个具有3个元素的数组。下面是[4],所以oof的元素是具有4个元素的数组。*说明这些元素都是指针。int完成了描述:oof是一个3元素数组,每个元素是由4个指向int的指针组成的数组。或者简单地说,它是一个3x4的指向int的指针数组。需要为12个指针留出存储空间。

    int (* uuf)[3][4];

       圆括号使修饰符*具有最高的优先级,所以uuf就是一个指向3x4的int数组的指针。需要为一个单个指针留出存储空间。

       函数和指针

       可以声明指向函数的指针。典型的用法是,一个函数指针可以作为一个函数的参数,告诉第二个参数使用哪一个函数。

       1° 函数指针是什么意思?函数是有地址的,这是因为函数的机器语言实现是由载入到内存的代码组成。指向函数的指针中保存着函数代码起始处的地址。

       2° 声明一个函数指针时,必须声明它指向的函数类型。要指定函数类型,就要指出函数的返回类型以及函数的参量类型。例如,考虑以下原型:

    void ToUpper(char *);  // 把字符串转换为大写

       函数ToUpper()的类型是“具有char *类型的参量,返回类型是void的函数”。要声明指向这种类型的函数的指针pf,可以这样做:

    void (*pf)(char *);  // pf是一个指向函数的指针

       结论:如果想要声明一个指向某一特定类型函数的指针,可以声明一个这种特定类型的函数,然后用一个(*pf)形式的表达式来代替函数名,以创建一个函数的指针。

       然而,省略掉圆括号会导致完全不同的情况:

    void *pf(char *); // pf是返回一个指针的函数,void *是通用指针

       有了函数指针之后,可以把适当类型的函数的地址赋给它,在这种场合中,函数名可以用来表示函数的地址。(也即函数名也是一个指针)。

       可以使用函数指针来访问函数。奇怪的是,有两个逻辑上不一致的语法规则来实现这样的操作。举例说明如下:

    void ToUpper(char *);
    void ToLower(char *);
    void (*pf)(char *);
    char mis[] = "Nina Metier";
    pf = ToUpper;
    (*pf)(mis);   // 把ToUpper作用于mis(语法一)
    pf = ToLower;
    pf(mis);  // 把ToLower作用于mis(语法二)

       第一种方法:因为pf指向ToUpper函数,*pf就是ToUpper函数,因此表达式(*pf)(mis)和ToUpper(mis)一样。从ToUpper和pf的声明中就能看出ToUpper和(*pf)是等价的。

       第二种方法:因为函数名是一个指针,可以互换地使用指针和函数名,因此pf(mis)与ToLower(mis)一样。从pf的赋值语句中就能看出pf和ToLwer是等价的。

       函数名的所有4种用法:

    原型声明中的函数名 int comp(int x, int y);
    函数调用中的函数名 status = comp(q, r);
    函数定义中的函数名 int comp(int x, int y){....}
    在赋值语句中用作指针的函数名 pfunct = comp;
    用作指针参数的函数名 slowsort(arr, n, comp);

       指向多维数组的指针

    int (*pz)[2]; // pz指向一个包含2个int值的数组(数组指针)

       注意:int * pax[2]:首先方括号和pax结合,表示pax是包含两个某种元素的数组,然后和*结合,表示pax是两个指针组成的数组。最后,用int来定义,表示pax是由两个指向int值的指针构成的数组。这种声明会创建两个指向单个int值的指针。(指针数组)

       

       指针之兼容性

       指针之间的赋值规则比数值类型的赋值更严格。举例说明:

       1° 

    int n = 5;
    double x;
    int * pi = &n;
    double * pd = &x;
    x = n; // 不需要进行类转换就直接把一个int数值赋给一个double类型的变量(隐藏的类型转换)
    pd = pi; // 编译时错误,原因:pd指向一个double类型的数值,pi指向一个int类型的数值

       2°

    int * pt;
    int (*pa)[3];
    int ar1[2][3];
    int ar2[3][2];
    int ** p2; // (指向int指针的)指针
    pt = &ar1[0][0]; // pt为指向一个int数值的指针,ar1[0][0]是一个int数值的变量
    pt = ar1[0]; // pt为指向一个int数值的指针,ar1[0]也为指向一个int数值的指针
    pt = ar1; // pt为指向一个int数值的指针,ar1指向由3个int值构成的数组(非法)
    pa = ar1; // pa指向由3个int值构成的数组,ar1也指向由3个int值构成的数组
    pa = ar2; // pa指向由3个int值构成的数组,ar2指向由2个int值构成的数组(非法)
    p2 = &pt; // p2是(指向int指针的)指针,&pt(头一次见,得记下来)也是(指向int指针的)指针
    p2 = ar2; // p2是(指向int指针的)指针,ar2是指向由2个int值构成的数组(非法)

       函数和二维数组

       处理二维数组的函数,举例说明:

    int junk[3][4] = { {2, 4, 5, 8}, {3, 5, 6, 9}, {12, 10, 8, 6} };

       junk是指向由4个int值构成的数组的指针,声明此类函数参量的方法如下:

    void somefunction(int (* pt)[4]);

       当且仅当pt是函数的形式参量时,也可以作如下这样声明:

    void somefunction(int pt[][4]); // 注意到第一对方括号里是空的。这个空的方括号表示pt是一个指针,这种变量的使用方法和junk一样

       复合文字

       c99新增了复合文字的特性。(了解就好)

       对于数组来说,复合文字看起来像是在数组的初始化列表前面加上用圆括号括起来的类型名。例如:

    int [2]){10, 20}; // 创建一个包含两个int值的无名称数组

       初始化一个复合文字时也可以省略数组大小,编译器会自动计算元素的数目:

    (int []){50, 20, 90}; // 有3个元素的复合文字

       由于这些复合文字没有名称,因此不可能在一个语句中创建他们,然后在一个语句中使用。而是必须在创建他们的同时通过某种方法来使用他们。

       1° 使用指针保存其位置

    int * pt1;
    pt1 = (int [2]){10, 20}; // 这个文字常量被标识为一个int数组。与数组名相同,这个常量同时代表首元素的地址,因此可以用它给一个指向int的指针赋值

       2° 也可以作为实际参数被传递给带有类型与之匹配的形式参量的函数

    int sum(int ar[], int n);
    ...
    int total3;
    total3 = sum((int []){4, 4, 4, 5, 5, 5}, 6); 

       3° 创建一个二维int数组并保存其地址

    int (*pt2)[4]; // 声明一个指向包含4个int的数组的指针
    pt2 = (int [2][4]){ {1, 2, 3, -9}, {4, 5, 6, -8} };

       复合文字和结构

       可以使用复合文字创建一个被用来作为函数参数或被赋值给一个结构的结构。语法是把类型名写在圆括号中,后跟一个用花括号括起来的初始化项目列表。例如,下面是一个struct book类型的复合文字:

    (struct book){"The Idiot", "Fyodor Dostoyevsky", 6.99}

       字符串与指针

       整个引号中的内容作为指向该字符串存储位置的指针。这一点与把数组名作为指向数组存储位置的指针类似。举例如下:

    /* 把字符串看作指针 */
    #include <stdio.h>
    int main()
    {
        printf("%s, %p, %c
    ", "We", "are", *"space farers");
        return 0;
    }

       “are”是个地址,%p输出字符串中第一个字符的地址,*“space farers”产生所指向地址中的值。输出结果如下:

    We, 00408024, s

       初始化一个存放字符串的字符数组和初始化一个指向字符串的指针

       结论:数组初始化是从静态存储区(数据段)把一个字符串复制给数组,而指针初始化只是复制字符串的地址。

       数组和指针的差别  

       举例说明之:

    char heart[] = "I love Tillie!";
    char *head = " I love Millie!";

       主要的差别在于数组名heart是个常量,而指针head则是个变量

    1. 首先,两者都可以使用数组符号:heart[i],head[i]
    2. 其次,两者都可以使用指针加法:*(heart + i),*(head + i)
    3. 但是只有指针可以使用增量运算符

       字符串数组

        举例说明:

    const char *mytal[LIM] = {
            "Adding numbers swiftly",
            "Multiplying accurately",
            "Stashing data",
            "Following instructions to the letter",
            "Understanding the C language"
    };

       mytal是一个由5个指向char的指针组成的数组(指针数组)。也就是说,mytal是一个一维数组,而且数组里的每一个元素都是一个char类型值的地址。第一个指针是mytal[0],它指向第一个字符串的第一个字符。第二个指针是mytal[1],它指向第二个字符串的开始。一般地,每一个指针指向相应字符串的第一个字符。(总之,mytal存放5个地址)。

       文件尾 EOF(End Of File)

       像getchar()/scanf()之类的函数在到达文件结尾时返回一个特殊值,而不去管操作系统是如何检测文件结尾的。赋予该值的名称是EOF(End Of File,文件尾)。通常EOF在stdio.h文件中定义,如下所示:

    #define EOF (-1)

       为什么是-1?一般情况下,getchar()返回一个范围在0到127之间的值,因为这些值是与标准字符集(ASCII)相对应的值。但如果系统识别一个扩展的字符集,则可能返回从0到255的值。在这种情况下,值-1都不对应任何字符,所以可以用它来表示文件结尾。   

       如果gets()遇到文件结尾,它就返回一个空(或0)地址。这个空地址被称为空指针,并用stdio.h里定义的常量NULL来表示。注意:一个指向NULL的指针,我们称之为空指针(也即野指针),意味着这个指针不指向任何变量。

       定义了一个指向int的指针变量,没有赋初值,就是个野指针,指向什么内存地址不知道。例如:

    int *p3;
    • 野指针的危害只是程序本身,不会影响到操作系统
    • 如果你做的是一个驱动程序或内核代码,那么可能是致命的,操作系统崩溃
    • 野指针也叫空悬指针,不能给野指针指向的内存区域赋值

       注意:不要混淆空指针和空字符。空指针是一个地址,而空字符是一个char类型的数据对象,其值为0。数值上两者都可以用0表示,但是它们的概念不同:NULL是一个指针,而0是一个char类型的常量。 

       比较字符串

    #include <stdio.h>
    #define ANSWER "Grant"
    int main(void)
    {
        char try[40];
    
        puts("Who is buried in Grant's tomb?");
        gets(try);
        while(try != ANSWER)
        {
            puts("No, that's wrong. Try again.");
            gets(try);
        }
        puts("That's right!");
    
        return 0;
    }

       ANSWER和try实际上是指针,因此比较式try != ANSWER并不检查这两个字符串是否一样,而是检查这两个字符串的地址是否一样。由于ANSWER和try被存放在不同的位置,所以这两个地址永远不会一样。

       如果pts1和pts2都是指向字符串的指针,则下面的表达式只复制字符串的地址而不是字符串本身:

    pts2 = pts1;

       strcmp()函数可以比较字符串内容(content)而不是字符串地址。

    strcmp(try, ANSWER) != 0

       size_t

       size_t类型是sizeof运算符返回的任何类型。C规定sizeof运算符返回一个整数类型,但是没有指定是哪种整数类型。因此size_t在一个系统上可以是unsigned int类型;在一个系统上,又可以是unsigned long类型。string.h文件为您的特定系统定义了size_t。stddef.h文件中包含有size_t类型的typedef或#define定义。其他文件(包括stdio.h)通过包含stddef.h来包含这个定义。

       命令行参数

    int main(int argc, char *argv[]) // argv是一个指向字符串的指针数组

       该形式和有形式参数的其他函数一样。还可以这样声明:

    int main(int argc, char **argv)

       这种声明意味着argv是一个指向“指向字符的指针”的指针。而char *argv[]有一个包含几个元素的数组。数组名是指向第一个元素的指针,因此argv指向argv[0],而argv[0]是一个指向字符的指针。因此,即便在原始的定义中,argv仍是一个指向“指向字符的指针”的指针。两种形式都可以使用。

       重新造轮子——atoi()

    int my_atoi(char *pstr)
    {
        int sign = 1;
        int num = 0;
    
        // 字符串不能为空
        if(pstr == NULL)
            return 0;
        // 去掉字符串前面的空格
        while(isspace(*pstr))
            pstr++;
        // 判断首位是否有符号,若为负号,sign等于-1;若为正号,sign不变,并且跳过符号位
        if(*pstr == '-')
            sign = -1;
        if(*pstr == '-' || *pstr == '+')
            pstr++;
        // 转换直到遇到非数字结束
        while(isdigit(*pstr))
        {
            num = num * 10 + (*pstr - '0'); // 这个计算太经典了!!!
            pstr++;
        }
        // 增加符号位
        num *= sign;
        return num;
    }
    View Code
  • 相关阅读:
    SpringMVC上传文件异常 400 Required MultipartFile parameter 'files' is not present postman
    idea乱码但是线上ok 解决方法
    mysql查询所有子节点 非递归
    nacos设置了默认读不到原因
    c
    a
    b
    chrome developer tool 文章
    每日一库:JS.Class
    用Jquery获取解析获取URL参数地址
  • 原文地址:https://www.cnblogs.com/yerenyuan/p/5062961.html
Copyright © 2020-2023  润新知