三道题

274 阅读8分钟

第 53 题

#include <stdio.h>

void fun(char **p) {
    ++p;
    printf("%s\n",*p);
}

int main(){
    char *c[] = {"China","Beijing","Liaoning","Shenyang"};
    fun(c);
    return 0;
}

分析代码:

首先说一个字符串的概念,C语言里字符串主要是通过两种形式表现

字符数组,char类型数组。比如:char s[] = "hello";
字符指针,char类型指针。比如: char* s = "world";    //打个比方,不要在乎重名

char类型数组的字符串,数组内的各个元素都是char类型的变量,每个元素在内存中按顺序存储,比如元素 'h' 的地址是 42,那么元素 'e' 的地址则是紧随其后,char占1字节,所以 'e' 的地址则为 43 。我们知道数组名代表数组首地址,我们使用如下形式,可以查看验证一下。

    char s[] = "hello";
    printf("element:%c  address:%d\n",s[0], s );
    printf("element:%c  address:%d\n",s[1], s+1);
    printf("element:%c  address:%d\n",s[2], s+2);
    printf("element:%c  address:%d\n",s[3], s+3);
    printf("element:%c  address:%d\n",s[4], s+4);

输出结果:

element:h  address:6422042
element:e  address:6422043
element:l  address:6422044
element:l  address:6422045
element:o  address:6422046

char类型指针的字符串 char* s = "world",其实指针 s 中只存放了 'w'的地址。 s 毕竟是一个char* 类型的指针,它只能存储一个字符的地址。之所以可以这么赋值,是因为编译器为字段代码的 "world" ,先在内存中分配空间,并按把 'w' 、'o' 、'r' 、'l' 、'd' 、'\0' 按顺序存储 ,然后再把 'w' 的地址赋值给 指针 s。我们可以验证一下。

    char* s = "world";
    printf("element:%c  address:%d\n",*s, s );
    printf("element:%c  address:%d\n",*(s+1), s+1);
    printf("element:%c  address:%d\n",*(s+2), s+2);
    printf("element:%c  address:%d\n",*(s+3), s+3);
    printf("element:%c  address:%d\n",*(s+4), s+4);

输出结果:

element:w  address:4210688
element:o  address:4210689
element:r  address:4210690
element:l  address:4210691
element:d  address:4210692

上面虽然举例了,但我们平常字符串不是这么用的呀,我们不都是用 %s 配合输入输出函数来玩字符串的嘛? 那我们就来说说这个 %s, 我们上面用了 %c 和 %d ,%c 对应的参数类型是 char ,%d 对应的参数类型是 int ,这个%s 对应的参数类型则是一个char指针,也就是 char* 类型 (我试过参数是其他类型指针的话,虽没报错但也不好使)。咱们继续看,%s 她对应的是char指针,也就是字符串的第一个字符的地址。

我们接下来回到本题,给出了这个东西

char* c[];

可以看出,这是一个数组,他的每一个元素,都是一个char指针。在本题中,他是这么赋值的。

char *c[] = {"China","Beijing","Liaoning","Shenyang"};

那么如此我们就能看出她,实际上是编译器给"China"、"Beijing"这些字符串文本的每个字符,先去内存开辟空间,并且把他们按照顺序存进内存里面,然后把每个字符串文本的首字符的地址,赋值给数组的每个元素。也就是:把'C'的地址赋值给 c[0]、'B'的地址赋值给 c[1]、'L'的地址赋值给 c[2]、'S'的地址赋值给 c[3]。大概如下:

printf("%d",c[0]);输出的则是字符'C'的地址
printf("%c",*c[0]);输出的则是字符 'C'  
printf("%s",c[0]);输出的则是字符串 s"China"

我们可以测试如下代码:

    char *c[] = {"China","Beijing","Liaoning","Shenyang"};
    printf("element:%c  address:%d  String:%s \n",*c[0],c[0],c[0]);
    printf("element:%c  address:%d  String:%s \n",*c[1],c[1],c[1]);
    printf("element:%c  address:%d  String:%s \n",*c[2],c[2],c[2]);
    printf("element:%c  address:%d  String:%s \n",*c[3],c[3],c[3]);

运行结果如下:

element:C  address:4210688  String:China
element:B  address:4210694  String:Beijing
element:L  address:4210702  String:Liaoning
element:S  address:4210711  String:Shenyang

我们来看字符C 和字符 B 的地址,4210688 和 4210694 中间相差了 5 个地址,因为编译器把他们是按顺序存储的,所以这5个地址则对应了'h'、'i'、'n'、'a'、'\0'。也就是c[0]对应'C'的地址,那么c[0]+1 对应的就是'h'的地址了,以此类推,我们继续用代码测试一下。( \0 不显示,所以不做测试)

printf("%c %c %c %c %c\n",*c[0],*(c[0]+1),*(c[0]+2),*(c[0]+3),*(c[0]+4);
printf("%c %c %c %c %c %c %c\n",*c[1],*(c[1]+1),*(c[1]+2),*(c[1]+3),*(c[1]+4),*(c[1]+5),*(c[1]+6));

运行结果:

C h i n a
B e i j i n g

我想到此,char *c[ ] 这个东西我们已经了解了,不过这道题,把 c 作为参数,传递给函数,并且用了 char **p 来接收这个地址。疑问来了,这个小家伙是什么东西?

char** p2 这个指针 p2 叫做二级指针,既然他叫二级指针,那么char * p1是不是就是一级指针呢? 对,是的,就是这么叫的。

  • 既然 char * p1他存放的是 char 类型字符变量的地址
  • 那么char ** p2存放的就是char * 类型指针变量的地址

是不是有点俄罗斯套娃的意思。既然如此,如果是char *** p3 的话,,,算了我没触碰过这个不敢乱说。

上面我们得到了char ** p 获取到了 char * 类型的,我们回顾一下这道题:

void fun(char **p) {
    ++p;
    printf("%s\n",*p);
}

int main(){
    char *c[] = {"China","Beijing","Liaoning","Shenyang"};
    fun(c);
    return 0;
}

我们看到给函数传的是 c 的首地址, 也就是 p 获取到了 c 的第一个元素 c[0] 的地址,使 p 指向 c[0] ,之后执行代码 ++p 使 p 指向 c[1] ,p 中存储的更变为 c[1] 的地址。

接下来使用 %s ,输出 *p ,我们已知 p 中存储的是 c[1] 的地址,那么 *p 则等同于 c[1] ,所以说 如果我们在主函数里面使用同样的输出函数,把参数 p 换成 c[1] ,效果是一样的,上代码:

void fun(char **p) {
    ++p;
    printf("%s\n",*p);
}

int main(){
    char *c[] = {"China","Beijing","Liaoning","Shenyang"};
    fun(c);
    printf("%s\n",c[1]);   //新增在此处
    return 0;
}

输出结果:

Beijing
Beijing

既然如此,本题完事。

59题

#include <stdio.h>

struct node {
    char data;
    struct node *next;
};
typedef struct node NODETYPE;

int main(){
    NODETYPE a,b,c,*h,*p;
    a.data = 'A';
    b.data = 'B';
    c.data = 'C';
    h = &a;
    a.next = &b;
    b.next = &c;
    c.next = '\0';
    p = h;
    while (p) {
        printf("%c",p->data);
        p = p->next;
    }
    printf("\n");
    return 0;
}

main函数里,首先定义了结构变量 a,b,c ,还有结构指针变量 h , p

NODETYPE a,b,c,*h,*p;

NODETYPE a,b,c,*h,*p; 这句代码这里之所以用了NODETYPE 就可以定义这些变量,是因为事先用了此代码 typedef struct node NODETYPE; ,之后使用NODETYPE,效果就等同于struct node ,具体内部有啥不同,先不研究。

typedef struct node NODETYPE;

NODETYPE a,b,c,*h,*p;
struct node a,b,c,*h,*p;  //效果相同

接下来是赋值,结构变量a b c 的成员 data 分别赋值上大写字母ABC

    a.data = 'A';
    b.data = 'B';
    c.data = 'C';

把结构变量 a 的地址赋值给结构指针 h。结构变量 b 的地址赋值给结构变量 a 的成员指针next,也就是赋值给a.next。把结构变量 c 的地址赋值给b.next。

    h = &a;
    a.next = &b;
    b.next = &c; 

我们上面说的都很容易理解,而下面这个语句,则比较迷惑,指针不止是能赋值地址么, '\0' 我们得说法是标记C字符串结尾的字符,或者称它为空字符,给指针赋值一个字符怎么回事?下面给出解答。

    c.next = '\0'

我们这道题意思其实是想让 c.next 成为一个空指针,空指针是一个不指向任何地方的指针,但不对呀,空指针应当赋值 NULL,而不是'\0'呀,空指针和空字符怎么能混淆么?这么写对么?不能混淆,不对,但是也能用......只是偶尔会出点问题,程序嘛,能跑就行又不是不能用(溜了溜了

这道题目的是,如果while( )中检查到指针== 0 (检测为空指针的方法),也就是他是个空指针,就退出循环,就这么点用处,咱也不能让出卷的改成NULL,所以好使就行。

接下来就是把 h 中的值,赋值给 p ,h中存储的是 a 的地址。之后进入循环

    p = h;
	while (p) {
        printf("%c",p->data);
        p = p->next;
    }
    printf("\n");
    return 0;

首先while检查一下 p 是不是空指针,不是,进入第一次循环:

  • 输出 p -> data,此时 p 指向 a,我们看此时a.data是何物即可,输出了'A'
  • 把 p->next 赋值给 p ,这时候 p 还指向 a ,a.next 中是b的地址,也就是 p 接下来开始指向 b 了。(本次循环完毕)

再检查一下 p 是不是空指针,指向 b 呢,不是,进入第二次循环:

  • 输出 p -> data,此时 p 指向 b,输出了'B'
  • 把 p->next 赋值给 p ,b.next 中是c的地址,也就是 p 接下来开始指向 c了。(本次循环完毕)

指向 c 呢 也不是空指针,第三次循环:

  • 输出 p -> data,此时 p 指向 c,输出了'C'
  • 把 p->next 赋值给 p ,c.next 是一个空指针啊,现在 p 也光荣成为一个空指针了。(本次循环完毕)

这时候循环结束了,换了个行,就return 0了,总之,输出的内容是 ABC

ABC

第60题

#include <stdio.h>

struct student{
    int num;
    char name[20];
}stu[2] = { {170101,"WangHong"},{170102,"LiMIng"} };

int main(){
    struct student *p;
    printf("ID NAME\n");
    for(p = stu; p < stu+2; p++) {
        printf("%d %s\n",p->num,p->name);
    }
    return 0;
}