必须弄懂的495个C语言问题

必须弄懂的495个C语言问题

1.1 我如何决定使用那种整数类型?

如果需要大数 值(大于32, 767 或小于?32, 767), 使用long 型。否则, 如果空间很重要(如有大数组或很多结构),
使用short 型。除此之外, 就使用int 型。如果严格定义的溢出特征很重要而负值无关紧要, 或者你希望在操作二进制位和字节时避免符号扩展的问题,
请使用对应的无符号类型。但是, 要注意在表达式中混用有符号和无符号值的情况。

尽管字符类型(尤其是无符号字符型) 可以当成“小” 整型使用,
但由于不可预知的符号扩展和代码增大有时这样做可能得不偿失。使用无符号字符型有所帮助;类似的问题参见问题12.1。

在选择浮点型和双精度浮点型时也有类似的权衡。但如果一个变量的指针必须为特定的类型时, 以上规则不再适用。

如果因为某种原因你需要声明一个有严格大小的变量, 确保象C99 的<inttypes.h> 那样用某种适当的typedef
封装这种选择。通常, 这样做唯一的好原因是试图符合某种外部强加的存储方案, 请参见问题20.3。

如果你需要操作超过C 的内置类型支持的超大变量, 请参见问题18.17。

参 考资料: [K&R1, Sec. 2.2 p. 34]; [K&R2, Sec. 2.2 p. 36, Sec. A4.2
pp.195-6, Sec. B11 p. 257]; [ISO, Sec. 5.2.4.2.1, Sec. 6.1.2.5]; [H&S, Secs.
5.1,5.2 pp.110-114]。

1.2 64 位机上的64 位类型是什么样的?

C99 标准定义了long long 类型, 其长度可以保证至少64 位,
这种类型在某些编译器上实现已经颇有时日了。其它的编译器则实现了类似longlong 的扩展。另一方面, 也可以实现16 位的短整型、32 位的整型和64
位的长整型, 有些编译器正是这样做的。

参见问题18.17。

参考资料: [C9X, Sec. 5.2.4.2.1, Sec. 6.1.2.5]

1.3 怎样定义和声明全局变量和函数最好?

首 先, 尽管一个全局变量或函数可以(在多个编译单元中) 有多处“声明”, 但是“定义” 却只能允许出现一次。定义是分配空间并赋初值(如果有)
的声明。最好的安排是在某个相关的.c 文件中定义, 然后在头文件(.h) 中进行外部声明, 在需要使用的时候, 只要包含对应的头文件即可。定义变量的.c
文件也应该包含该头文件, 以便编译器检查定义和声明的一致性。

这条规则提供了高度的 可移植性: 它和ANSI C 标准一致, 同时也兼容大多数ANSI 前的编译器和连接器。Unix
编译器和连接器通常使用“通用模式” 允许多重定义, 只要保证最多对一处进行初始化就可以了; ANSI C 标准称这种行为为“公共扩展”,
没有语带双关的意思。

可以使用预处理技巧来使类似DEFINE(int, i);的语句在一个头文件中只出现一次,
然后根据某个宏的设定在需要的时候转化成定义或声明。但不清楚这样的麻烦是否值得。

如果希望让编译器检查声明的一致性, 一定要把全局声明放到头文件中。特别是, 永远不要把外部函数的原型放到.c 文件中:
通常它与定义的一致性不能得到检查, 而矛盾的原型比不用还糟糕。

参见问题10.4 和18.6。

参 考资料: [K&R1, Sec. 4.5 pp. 76-7]; [K&R2, Sec. 4.4 pp. 80-1];
[ISO, Sec.6.1.2.2, Sec. 6.7, Sec. 6.7.2, Sec. G.5.11]; [Rationale, Sec.
3.1.2.2]; [H&S, Sec. 4.8pp. 101-104, Sec. 9.2.3 p. 267]; [CT&P, Sec. 4.2
pp. 54-56].

1.4 extern 在函数声明中是什么意思?

它可以用作一种格式上的提示表明函数的定义可能在另一个源文件中, 但在extern int f();和int f();之间并没有实质的区别。

参考资料: [ISO, Sec. 6.1.2.2, Sec. 6.5.1]; [Rationale, Sec. 3.1.2.2];
[H&S,Secs. 4.3,4.3.1 pp. 75-6].

1.5 关键字auto 到底有什么用途?

毫无用途;它已经过时。参见问题20.32。

参考资料: [K&R1, Sec. A8.1 p. 193]; [ISO, Sec. 6.1.2.4, Sec. 6.5.1;];
[H&S,Sec. 4.3 p. 75, Sec. 4.3.1 p. 76].

1.6 我似乎不能成功定义一个链表。我试过typedef struct { char*item; NODEPTR next; }
*NODEPTR; 但是编译器报了错误信息。难道在C语言中一个结构不能包含指向自己的指针吗?

C 语言中的结构当然可以包含指向自己的指针; [K&R2, 第6.5 节] 的讨论和例子表明了这点。NODEPTR
例子的问题是在声明next 域的时候typedef 还没有定义。为了解决这个问题, 首先赋予这个结构一个标签(“struct
node”)。然后,声明“next” 域为“struct node *”, 或者分开typedef 定义和结构定义,
或者两者都采纳。以下是一个修改后的版本:
struct node {
char *item;
struct node
*next;
};
typedef struct node *NODEPTR;

至少还有三种同样正确的方法解决这个问题。

在用typedef 定义互相引用的两个结构时也会产生类似的问题, 可以用同样的方法解决。

参见问题2.1。

参考资料: [K&R1, Sec. 6.5 p. 101]; [K&R2, Sec. 6.5 p. 139]; [ISO,
Sec.6.5.2, Sec. 6.5.2.3]; [H&S, Sec. 5.6.1 pp. 132-3]。

1.7 怎样建立和理解非常复杂的声明?例如定义一个包含N 个指向返回指向字符的指针的函数的指针的数组?

这个问题至少有以下3 种答案:

1. char *(*(*a[N])())();

2. 用typedef 逐步完成声明:
typedef char *pc; /* 字符指针*/
typedef pc fpc();
/* 返回字符指针的函数*/
typedef fpc *pfpc; /* 上面函数的指针*/
typedef pfpc fpfpc(); /*
返回函数指针的函数*/
typedef fpfpc *pfpfpc; /* 上面函数的指针*/
pfpfpc a[N]; /*
上面指针的数组*/

3. 使用cdecl 程序, 它可以把英文翻译成C 或者把C 翻译成英文:
cdecl> declare a as array of
pointer to function returning

pointer to function returning pointer to char

char *(*(*a[])())()
通过类型转换, cdecl 也可以用于解释复杂的声明,
指出参数应该进入哪一对括号(如同在上述的复杂函数定义中)。参见问题18.1。

一本好的C 语言书都会解释如何“从内到外” 解释和理解这样复杂的C
语言声明(“模拟声明使用”)。

上文的例子中的函数指针声明还没有包括参数类型信息。如果参数有复杂类型, 声明就会变得真正的混乱了。现代的cdecl 版本可以提供帮助。

参考资料: [K&R2, Sec. 5.12 p. 122]; [ISO, Sec. 6.5ff (esp. Sec. 6.5.4)];
[H&S,Sec. 4.5 pp. 85-92, Sec. 5.10.1 pp. 149-50]。

1.8 函数只定义了一次, 调用了一次, 但编译器提示非法重定义了。

在 范围内没有声明就调用(可能是第一次调用在函数的定义之前) 的函数被认为返回整型(int) (且没有任何参数类型信息),
如果函数在后边声明或定义成其它类型就会导致矛盾。所有函数(非整型函数一定要)
必须在调用之前声明。另一个可能的原因是该函数与某个头文件中声明的另一个函数同名。

参见问题11.4 和15.1

参考资料: [K&R1, Sec. 4.2 p. 70]; [K&R2, Sec. 4.2 p. 72]; [ISO, Sec.
6.3.2.2];[H&S, Sec. 4.7 p. 101].

1.9 main() 的正确定义是什么? void main() 正确吗?

参见问题11.11 到11.16。(这样的定义不正确)。

1.10 对于没有初始化的变量的初始值可以作怎样的假定?如果一个全局变量初始值为“零”,
它可否作为空指针或浮点零?

具 有“静态” 生存期的未初始化变量(即, 在函数外声明的变量和有静态存储类型的变量) 可以确保初始值为零, 就像程序员键入了“=0”
一样。因此, 这些变量如果是指针会被初始化为正确的空指针, 如果是浮点数会被初始化为0.0 (或正确的类型, 参见第5 章)。

具有“自动” 生存期的变量(即, 没有静态存储类型的局部变量) 如果没有显示地初始化,
则包含的是垃圾内容。对垃圾内容不能作任何有用的假设。这些规则也适用于数组和结构(称为“聚合体” ); 对于初始化来说, 数组和结构都被认为是“变量”。

用malloc() 和realloc() 动态分配的内存也可能包含垃圾数据, 因此必须由调用者正确地初始化。用calloc() 获得的内存为全零,
但这对指针和浮点值不一定有用(参见问题7.26 和第5 章)。

参 考资料: [K&R1, Sec. 4.9 pp. 82-4]; [K&R2, Sec. 4.9 pp. 85-86];
[ISO, Sec.6.5.7, Sec. 7.10.3.1, Sec. 7.10.5.3]; [H&S, Sec. 4.2.8 pp. 72-3,
Sec. 4.6 pp. 92-3,Sec. 4.6.2 pp. 94-5, Sec. 4.6.3 p. 96, Sec. 16.1 p. 386.]。

1.11 代码int f() { char a[] = "Hello, world!";} 不能编译。

可能你使用的是ANSI 之前的编译器, 还不支持“自动聚集”(automatic aggregates,即非静态局部数组、结构和联合)
的初始化。参见问题11.28。

1.12 这样的初始化有什么问题?char *p = malloc(10); 编译器提示“非法初始式”
云云。

这个声明是静态或非局部变量吗?函数调用只能出现在自动变量(即局部非静态变量) 的初始式中。

1.13 以下的初始化有什么区别?char a[] = "string literal"; char *p= "string
literal"; 当我向p[i] 赋值的时候, 我的程序崩溃了。

字 符串常量有两种稍有区别的用法。用作数组初始值(如同在char a[] 的声明中), 它指明该数组中字符的初始值。其它情况下,
它会转化为一个无名的静态字符数组, 可能会存储在只读内存中, 这就是造成它不一定能被修改。在表达式环境中, 数组通常被立即转化为一个指针(参见第6 章),
因此第二个声明把p 初始化成指向无名数组的第一个元素。
为了编译旧代码, 有的编译器有一个控制字符串是否可写的开关。

参见问题1.11、6.1、6.2 和6.6。

参考资料: [K&R2, Sec. 5.5 p. 104]; [ISO, Sec. 6.1.4, Sec. 6.5.7];
[Rationale,Sec. 3.1.4]; [H&S, Sec. 2.7.4 pp. 31-2]。

1.14 我总算弄清除函数指针的声明方法了, 但怎样才能初始化呢?

用下面这样的代码
extern int func();
int (*fp)() = func;

当一个函数名出现在这样的表达式中时, 它就会“蜕变” 成一个指针(即, 隐式地取出了它的地址), 这有点类似数组名的行为。

通常函数的显示声明需要事先知道(也许在一个头文件中)。因为此处并没有隐式的外部函数声明(初始式中函数名并非一个函数调用的一部分)。

参见问题1.8 和4.8。

必须弄懂的495个C语言问题,布布扣,bubuko.com

时间: 2024-10-15 22:55:38

必须弄懂的495个C语言问题的相关文章

彻底弄懂 JavaScript 执行机制

本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序.因为javascript是一门单线程语言,所以我们可以得出结论: javascript是按照语句出现的顺序执行的 看到这里读者要打人了:我难道不知道js是一行一行执行的?还用你说?稍安勿躁,正因为js是一行一行执行的,所以我们以为js都是这样的: let a

如何继承Date对象?由一道题彻底弄懂JS继承。

前言 见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正. ----------长文+多图预警,需要花费一定时间---------- 故事是从一次实际需求中开始的... 某天,某人向我寻求了一次帮助,要协助写一个日期工具类,要求: 此类继承自Date,拥有Date的所有属性和对象 此类可以自由拓展方法 形象点描述,就是要求可以这样: // 假设最终的类是 MyDate,有一个getTest拓展方法 let date = new MyDate(); // 调用Date的方法,输出GM

这一次,彻底弄懂 JavaScript 执行机制

本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我. 文章转自:https://juejin.im/post/59e85eebf265da430d571f89 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序.因为javascript是一门单线程语言,所以我们可以得出结论: javascript是按照语句出现的顺序执行的 看到这里读者要打人了:我难道不知道js

摘录和再编:彻底弄懂JS执行机制

网文: https://juejin.im/post/59e85eebf265da430d571f89 并发模型和事件循环:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop Node.js事件循环,Timers, process.nextTick() javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变.所以一切javascript版的

python中self与__init__怎么解释能让小白弄懂?

python中self与__init__怎么解释能让小白弄懂? 这个问题其实没那么简单. 只说一下自己的理解. python 里所有的 object 都有三个属性, 标识(identity), 类型(type) 和值(value). 其中 identity 可以用 id 函数获得, CPython 里的实现是 object 的内存地址. 值就是 object 具体存放的数据, 而 type, object 的类型, 决定了可以对数据所进行的操作. 这里举个例子, tuple (1, 2, 3)和

【CodeForces】343D Water tree (线段树好题!还未弄懂)

/* 此题的方法除了用线段树求子树,通过标记父亲,更新儿子的方法,来更新祖先,学习了. 对于建树的方法由于并没有说明父亲与儿子的顺序,所以需要通过两次添加. 并且pre变量可以获得父亲的位置,还未弄懂! */ #define _CRT_SECURE_NO_WARNINGS #include<cstring> #include<cstdio> #include<iostream> #include<algorithm> using namespace std;

【转】彻底弄懂最短路径问题(图论)

来源:彻底弄懂最短路径问题 http://www.cnblogs.com/hxsyl/p/3270401.html P.S.根据个人需要,我删改了不少 问题引入 问题:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径.解决最短路的问题有以下算法,Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法,另外还有著名的启发式搜索算法A*,不过A*准备单独出一篇,其中Floyd算法可以求解任意两点间的最短路径的长度.笔者认为任意一个最

《你必须知道的495个C语言问题》笔记--库函数

怎样把数字转为字符串(与atoi相反)?有itoa函数吗? 用sprintf就可以了: sprintf(string, "%d", number); 同理,也可以同sprintf把long型或浮点型转换成字符串(使用%ld或%f),也就是说,可以把sprintf看成是atol或者atof的 反函数. 怎样在日期上加n天?怎样取得两个日期的时间间隔? 第一个问题,mktime接受没有规范话的日期,所以可以用一个日期的struct tm结构,直接在tm_mday域上进行加减,然后 调用mk

《你必须知道的495个C语言问题》笔记--杂项

如何进行移位操作? 因为左移操作(<<)不会导致符号位出现缺位,不考虑符号位,低位补0即可.所以对于无符号和有符号数来说,均为逻辑左移. 右移操作(>>)会涉及到符号位出现缺位的问题,所以在有符号数的右移操作时要考虑符号位怎么补的问题.对于无符号数来说, 最左侧补0,即逻辑右移:对于有符号来说,最左侧补符号位,即符号右移. 实践: #include <stdio.h> int main(void) { unsigned rui; int ri; unsigned int