技术笔试面试

《帮你度过C语言新手阶段》系列之三

真正深入学习C语言已经3年有余了。不过还是有很多知识点没有记下来,在此总结一下,也帮助C语言新手度过新手难关。

如果你已经认真阅读过谭浩强C,那么复习如下知识大约需要1个小时:)

本篇是《帮你度过C语言新手阶段》系列的最后一篇。

第三十五关:

char *a;

scanf(“%s”,a);

上述程序是绝对禁止的,是错误的。正确的写法如下:

char *a,str[10];

a=str;

scanf(“%s”,a);

第三十六关:

用指针变量来指向一个格式字符串,可以用它来代替printf函数中的格式字符串,如:

char *format;

format=”a=%d,b=%f\n”;

printf(format,a,b);

其实format也可以定义为char format[]形式,但不建议,因为用指针的话,可以很方便指向另一个格式字符串。

第三十七关:

声明一个指向函数的指针:int (*p)(); 定义p是一个指向函数的指针变量,此函数带回整型的返回值。

所以定义指向函数的指针时,只需要注意返回值相匹配即可,而形参部分不需要考虑。

注意,如果写成int *p();由于()的优先级高于*,就变成了声明一个函数,它的返回值是指向整型的指针。

使用p的方法:

int max(int a, int b);

p=max;

num=(*p)(3,5);

在一个程序中,一个指针变量可以先后指向返回类型相同的不同函数。

第三十八关:

用指向函数的指针做函数参数:

sub(int (*x1)(int),int (*x2)(int,int));

这里的参数列表可以选择不写(不推荐此种写法),即sub(int (*x1)(),int (*x2)());,不过如果写参数,就必须要写全写正确。

虽然C允许在参数中用()省略掉形参表,但是,从一个良好的程序风格来看,还是应当禁止这样的使用。

第三十九关:

指针数组的定义:

char *name[]={“follow me”,”basic”,”great wall”,”fortran”,”computer”};

name代表该指针数组的首地址,name+i是name[i]的地址。

程序举例:

char *name[]={“follow me”,”basic”,”great wall”,”fortran”,”computer”};

char **p;

int i;

for (i=0;i<5;i++){

p=name+i;

printf(“%s\n”,*p);

}

第四十关:

struct定义一定不要忘了最后的分号:

struct 结构体名

{成员列表};

在引用结构体变量的域时,*p.num相当于*(p.num),因为.的优先级是最高的!

第四十一关:

共用体的定义为:

union 共用体名称

{

成员列表;

}变量列表;

例如:

union data

{

int i;

char ch;

float f;

}a,b,c;

共用体变量所占的内存长度是最长的成员的长度。

在引用共用体变量时应十分注意当前存放在共用体变量中的究竟是哪一个成员。

&a、&a.i、&a.ch、&a.f都是同一个地址值。

不能对共用体变量名赋值,也不能企图引用变量名得到一个值,更不能在定义共用体时对它初始化。

第四十二关:

声明枚举类型的举例:

enum weekday{sun,mon,tue,wed,thu,fri,sat};

声明了枚举类型之后,就可以用枚举类型来定义变量:

enum weekday workday,weekend;

对于枚举元素,C语言编译时的顺序使它们的值为0,1,2…

workday=mon;

printf(“%d”,workday);

则会显示1

当然你可以改变这种现状:

enum weekday{sun=7,mon=1,tue,wed,thu,fri,sat}workday,weekend;

此时,sun=7,mon=1,tue=2…以此类推

一个整数不能直接赋值给一个枚举变量,而要先进行类型转换:

workday=(enum workday)2;

第四十三关:

用typedef声明结构体类型:

typedef struct

{

int month;

int day;

int year;

}DATE;

这样就声明了新的类型DATE,然后就可以用DATE来声明结构体变量了。如DATE birthday;DATE *p;

用typedef声明数组类型:

typedef int NUM[100];

然后就可以用NUM来定义数组变量:

NUM n;

第四十四关:

C语言的位运算有6个运算符:

1 与&

2 或|

3 异或^

4 取反~

5 左移<<

6 右移>>

位运算的运算量只能是整型或字符型,不能是其他类型,否则会报错。

按位与:用于清零或提取某一位

按位或:用于置1

异或:用于交换两个整型值或两个字符型值。如a=a^b;b=b^a;a=a^b;

取反:令最低位置1。如a=a&~1。此方法适用于在32位和64位机之间兼容。

左移:相当于乘以2,右补0。

右移:如果首位为0,则左补0;如果首位为1,则分为逻辑右移和算术右移两种情况。

逻辑右移:左补0

算术右移:左补1

第四十五关:

位段的概念非常重要,在编写网络程序常会用到。

struct packet_data

{

unsigned a:2;

unsigned b:6;

unsigned c:4;

unsigned d:4;

int i;

}data;

当然也可以不恰好占满一个字节,如

struct packet_data

{

unsigned a:2;

unsigned b:3;

unsigned c:4;

int i;

}data;

这样的话a,b,c会占去2字节中的前9位,而后7位会空闲下来,而i会从另一个新字节开头开始。

在引用位域时,要特别注意其最大值范围,如占2位,那么最大值为3.

位段成员的类型只能指定为unsigned int或int型。

若要强制一个域从新字节开始,那么可以这样:

unsigned a:1;

unsigned b:2;

unsigned :0;

unsigned c:3;

此时c会从一个新字节开始存储。

位段可以用%d来输出。

可以定义无名位段,表示这些位我不用:

unsigned a:1;

unsigned :2; //这两位空间我不用

unsigned c:3;

第四十六关:

在缓冲文件系统中,有一个概念叫做“文件指针”。

在stdio.h中有关FILE结构体类型的声明:

typedef struct
{
short level;    //缓冲区满或空的程度
unsigned flags;    //文件状态标志
char fd;    //文件描述符
unsigned char hold;    //如无缓冲区不读取字符
short bsize;    //缓冲区的大小
unsigned char *buffer;    //数据缓冲区的位置
unsigned char    *curp;    //指针、当前的指向
unsigned istemp;    //临时文件,指示器
short token;    //用于有效性检查
}FILE;

可见,其实FILE也是一个由typedef定义过的类型,其本质是一个结构体类型,其中有很多域,存储着和这个文件相关的各种各样的信息。

在进行文件读写操作时,建议不要使用fprintf和fscanf函数,而尽量使用fread和fwrite函数。

over~

4条评论

  1. 即使C允许在参数中用()省略掉形参表,从一个良好的程序风格来看,我个人觉得还是应当禁止这样的使用。

    这也是讲授C语言的书籍中会提到“在函数没有参数时写明void”的一个原因。

    ps: 我在上海,不在北京 :P

  2. To: Lee.MaRS
    非常感谢Lee.MaRS对于文中错误的指正:)
    我已经修改了文中的错误,并计划在明后天写一个专门的文章来描述此知识点。
    再次感谢Lee.MaRS关注本博客,并为此提出的诸多建议。
    以后争取不出技术上的低级错误。:)
    ps:请问Lee.MaRS在北京么?

  3. 引用:
    “第三十八关:

    用指向函数的指针做函数参数:

    sub(int (*x1)(int),int (*x2)(int,int));

    其实,此处的参数规格完全可以乱写或者不屑,因为函数指针只是接受函数的首地址,和后面的参数啥的都没关系。”

    此处不仅不正确,而且严重误导。

    1. 指针也是有类型的,不要以为指针就是一个地址值而已。传入非正确类型的参数将会导致程序行为异常。
    [leemars@LeeMaRS-Notebook shm]$ cat test.c
    #include

    void funa(int (*f1)(int, int), int (*f2)(int)) {
    printf(“%d\n”, (*f1)(3, 4));
    printf(“%d\n”, (*f2)(5));
    }

    int funb(int p1) {
    return -p1;
    };

    int func(int p1, int p2) {
    return p1 + p2;
    }

    int main() {
    funa(funb, func);
    funa(func, funb);
    return 0;
    }
    [leemars@LeeMaRS-Notebook shm]$ gcc test.c
    test.c: In function ‘main’:
    test.c:17: warning: passing argument 1 of ‘funa’ from incompatible pointer type
    test.c:17: warning: passing argument 2 of ‘funa’ from incompatible pointer type
    [leemars@LeeMaRS-Notebook shm]$ ./a.out
    -3
    2
    7
    -5

    可以看到如果不按类型传参数,将会得到警告,同时程序执行的结果错误。

    2. 进一步,在参数的声明决定了在函数中调用时的函数规格,乱定义将直接导致编译不通过。
    [leemars@LeeMaRS-Notebook shm]$ vim test.c
    [leemars@LeeMaRS-Notebook shm]$ cat test.c
    #include

    void funa(int (*f1)(int), int (*f2)(int)) {
    printf(“%d\n”, (*f1)(3, 4));
    printf(“%d\n”, (*f2)(5));
    }

    int funb(int p1) {
    return -p1;
    };

    int func(int p1, int p2) {
    return p1 + p2;
    }

    int main() {
    funa(funb, func);
    funa(func, funb);
    return 0;
    }
    [leemars@LeeMaRS-Notebook shm]$ gcc test.c
    test.c: In function ‘funa’:
    test.c:4: error: too many arguments to function ‘f1’
    test.c: In function ‘main’:
    test.c:17: warning: passing argument 2 of ‘funa’ from incompatible pointer type
    test.c:18: warning: passing argument 1 of ‘funa’ from incompatible pointer type

    3. 程序员一定要养成良好的编程习惯,否则将给别人给自己带来数不清的麻烦。

  4. 看博主应该和我是同龄人,很牛啊~
    网站也做得不错,也后多多交流哈~
    有个很弱的问题发到你邮箱的望不吝赐教~

发表您的评论

请您放心,您的信息会被严格保密。必填项已标识 *