《C Primer Plus》学习笔记
使用C语言的理由
- 强大的控制结构
- 快速
- 紧凑的代码——程序更小
- 可移植到其他计算机
链接器的作用
将目标代码、系统的标准启动代码和库代码结合在一起,并将它们存放在单个文件,即可执行文件中。
ANSI C的精神
- 相信程序员
- 不妨碍程序员做需要完成的事情
- 让语言保持短小简单
- 只提供一种方法来执行一个操作
- 使程序运行速度快,即使不完成保证其可移植性
C99标准的新目标
- 国际化
- 修正其不足
- 改进计算的实用性
main函数的规范函数名
int main (void)
_Bool类型
_Bool类型由C99引入,用于表示布尔值,即逻辑值true与false。在头文件stdbool.h中定义了bool可作为与C++类似的类型来代替_Bool。
可移植的类型:inttypes.h
C99提供了一个可选的名字集合,以确切地描述有关信息。例如:int16_t表示一个16位有符号整数类型,uint32_t表示一个32位无符号整数类型。
使用确切长度类型的一个潜在问题是某个系统可能不支持一些选择。为了解决这个问题,C99标准定义了第2组名字集合。这些名字保证所表示的类型至少大于指定长度的最小类型,被称为“最小长度类型”。例如,int_least8_t是可以容纳8位有符号数的那些类型中长度最小的一个的别名。
C99也定义了一组可使计算达到最快的类型集合。这组集合被称为“最快最小长度类型”。例如,把int_fast8_t定义为系统中对8位有符号数而言计算最快的整数类型的别名。
最后,对于某些程序员有时会需要系统最大的可能整数类型。为此,C99把intmax_t定义为最大的有符号整数类型,即可以容纳任何有效的有符号整数值的类型;类似地,把uintmax_t定义为最大的无符号整数类型。
C99还提供了对这些类型数据进行输入输出的方法。例如,用宏定义PRID16代替d,用于在printf中替换%d。
刷新输出
printf函数语句首先将输出传递给一个被称为缓冲区的中介存储区域。缓冲区中的内容再不断地被传递给屏幕。标准C规定在一下几种情况下降缓冲区内容传给屏幕:
- 缓冲区满的时候
- 遇到换行符的时候
- 需要输入的时候
浮点值的上溢和下溢
当计算结果是一个大得不能表达的数时,会发生上溢。现在的C语言要求为上溢的浮点数赋予一个表示无穷大的特殊值,printf函数显示此值为inf或infinity。
当除以一个十分小的数时,可能出现下溢。这时,计算机会右移若干位,空出前面,并舍弃最后的位,从而损失有效数字。此过程称为下溢。C将损失了类型精度的浮点值称为低于正常的。
特殊的浮点值NaN
在进行浮点数运算时,若出现算术错误,函数不会报错,而是返回一个特殊的值NaN(Not-a-Number)。printf函数显示此值为nan,NaN或其他类似形式。
系统定义的明显常量
C头文件limits.h和float.h分别提供有关整数类型和浮点类型的大小限制的详细信息。
limits.h中的一些常量:
符号常量 |
含义 |
CHAR_BIT | 一个 char 的位数 |
CHAR_MAX | char 类型的最大值 |
CHAR_MIN | char 类型的最小值 |
SCHAR_MAX | signed char 类型的最大值 |
SCHAR_MIN | signed char 类型的最小值 |
UCHAR_MAX | unsigned char 类型的最大值 |
SHRT_MAX | short 类型的最大值 |
SHRT_MIN | short 类型的最小值 |
USHRT_MAX | unsigned short 类型的最大值 |
INT_MAX | int 类型的最大值 |
INT_MIN | int 类型的最小值 |
UINT_MAX | unsigned int 类型的最大值 |
LONG_MAX | long 类型的最大值 |
LONG_MIN | long 类型的最小值 |
ULONG_MAX | unsigned long 类型的最大值 |
LLONG_MAX | long long 类型的最大值 |
LLONG_MIN | long long 类型的最小值 |
ULLONG_MAX | unsigned long long 类型的最大值 |
float.h文件中的一些符号常量:
符号常量 |
含义 |
FLT_MANT_DIG | float类型的尾数位数 |
FLT_DIG | float类型的最少有效数字位数(十进制) |
FLT_MIN_10_EXP | 带全部有效数字的float类型的负指数的最小值(以10为底) |
FLT_MAX_10_EXP | float类型的正指数的最大值(以10为底) |
FLT_MIN | 保留全部精度的float类型正数的最小值 |
FLT_MAX | float 类型正数的最大值 |
FLT_EPSILON | 1.00和比1.00大的最小的float类型值之间的差值 |
printf函数的参数传递机制
1: float n1;
2: double n2;
3: long n3;
4: long n4;
5:
6: printf(“%ld %ld %ld %ld”, n1, n2, n3, n4);
该调用告诉计算机把变量n1、n2、n3和n4的值传递给计算机,计算机把它们放置到被称为堆栈的一块内存区域中来实现。计算机根据变量的类型而非转换说明符把这些值放到堆栈中。所以,n1在堆栈中占用8个字节(float被转换成了double)。同样,n2占用了8个字节,而n3和n4则分别占用4个字节。然后控制权转移到 printf 函数。该函数从堆栈把值读出来,但是在读取时,它根据转换说明符去读取。%ld 说明符指出,printf 应该读取4个字节,所以 printf 函数在堆栈中读取前4个字节作为它的第一个值。这就是n1的前半部分,它被解释成一个长整数。下一个 %ld 说明符再读取4个字节;这就是n1的后半部分,它被解释成第二个长整数。同样,%ld的第三个和第四个实例使得n2的前半部分和后半部分被读取出来,并被解释成两个长整数。所以,虽然n3和n4的说明符都正确,但是 printf 函数仍然读取了错误的字节。
printf 函数和 scanf 函数的*修饰符
printf 和 scanf 都可以使用*修饰说明符的意义,但是它们的方式不同。
printf 函数中的*修饰符:
假定你不想事先指定字段宽度,而是希望由程序来指定该值,那么可以在字段宽度部分使用*代替数字来达到目的,但你也必须使用一个参数来告诉函数字段宽度是什么。也就是说,如果转换说明符是%*d,那么参数列表中应该包含一个*的值和一个d的值。该技术 也可以和浮点值一起使用来指定精度和字段宽度。
实例:
1: #include <stdio.h>
2:
3: int main(void)
4: {
5: unsigned width, precision;
6: int number = 256;
7: double weight = 242.5;
8:
9: printf("What field width?\n");
10: scanf("%d", &width);
11: printf("The number is: %d:\n", width, number);
12: printf("Now enter a width and a precision:\n");
13: scanf("%d %d", &width, &precision);
14: printf("Weight = %*.*f\n", width, precision, weight);
15:
16: return 0;
17: }
在scanf中*提供截然不同的服务。当把它放在%和说明符之间时,它使函数跳过相应的输入项目。
实例:
1: #include <stdio.h>
2:
3: int main(void)
4: {
5: int n;
6:
7: printf("Please enter three integgers:\n");
8: scanf("%*d %*d %*d %d", &n);
9: printf("The last interger was %d\n", n);
10:
11: return 0;
12: }
如果程序需要读取一个文件中某个特定的列,那么该功能将非常有用。
确认输入了一个整数
1: int get_int(void)
2: {
3: int input;
4: char ch;
5:
6: while(scanf("%d", &input) != 1)
7: {
8: while ((ch = getchar()) != '\n')
9: putchar(ch);
10: printf(" is not an integer.\n");
11: printf("Please enter an integer value.\n");
12: }
13: return input;
14: }
无参数函数
假设使用以下函数原型:
void print_name();
这时一个ANSI C编译器会假设你没有用函数原型声明函数,它就不会进行参数检查。因此,为了表示一个函数确定不使用参数,需要在圆括号内加入void关键字:
void print_name(void);
复浮点数(C99)(<complex.h>)
C99识别两种类型的浮点数:实数浮点数类型和复浮点数类型。两种类型共同组成了浮点类型。
复浮点数具有两个部分:一个实部和一个虚部。C99内部使用一个二维数组来表示复数,第一个部分为实部,第二个部分作为虚部。有3中复浮点类型:
float _Complex | 代表实部和虚部都是float值 |
double _Complex | 代表实部和虚部都是double值 |
long _Complex | 代表实部和虚部都是long double值 |
虚数只有虚部。这3中类型是:
float _Imaginary | 代表虚部是float值 |
double _Imaginary | 代表虚部是double值 |
long _Imaginary | 代表虚部是long double值 |
宏定义I表示i,也就是-1的平方根。
示例代码:
#include <complex.h> double _Complex z = 3.0; double _Complex w = 4.0 * I; double _Complex u = 6.0 - 8.0 * I;
指定初始化项目(C99)
C99增加了一种新特性:指定初始化项目(designated initializer)。此特性允许选择对某些元素进行初始化。例如:要对数组的最后一个元素初始化。按照传统的C初始化语法,需要对每一个元素都初始化之后,才可以对最后的元素进行初始化:
int arr[6] = {0, 0, 0, 0, 0, 212} // 传统语法
而C99规定,在初始化列表中使用带有方括号的元素下标可以指定某个特定的元素:
int arr[6] = {[5] = 212}; // 把arr[5]初始化为212
对于通常的初始化,在初始化一个或多个元素后,未经初始化的元素都将被设置为0。
附加的指定初始化规则:
1. 如果在一个指定初始化项目后跟有不止一个值,则这些数值将用来对后续的数组元素初始化。
2. 如果多次对一个元素进行初始化,则最后的一次有效。
代码示例:
// 使用指定初始化项目 #include <stdio.h> #define MONTHS = 12 int main (void) { int days[MONTHS] = {31, 28, [4] = 31, 30, 31, [1] = 29}; int i; for (i = 0; i < MONTHS; i++) printf("%2d %d\n", i + 1; days[i]); return 0; }
输出:
1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0
9 0
10 0
11 0
12 0
对结构使用typedef:
typedef struct complex { float real; float imag; } COMPLEX;
或者省去结构的标记:
typedef struct { double x; double y; } rect;
如果两个结构的声明都不使用标记,但是使用同样的成员(成员名和类型都匹配),那么C认为这两个结构具有相同的类型。