类型 | 解释 | 说明 | 注意事项 | 本地字节数 |
---|---|---|---|---|
short | 有符号短整数 | 完整形式signed short int ,singed 和int 可以省略 | 最左边一位表示符号,0 为正数,1 为负数 | 2 |
unsigned short | 无符号短整数 | 完整形式unsigned short int ,int 可以省略 | 全部位占满 | 2 |
int | 有符号整数 | 完整形式signed int ,singed 可以省略 | 最左边一位表示符号,0 为正数,1 为负数 | 4 |
unsigned int | 无符号整数 | 全部位占满 | 4 | |
long | 有符号长整数 | 完整形式为signed long int ,singed 和int 可以省略 | 最左边一位表示符号,0 为正数,1 为负数 | 8 |
unsigned long | 无符号长整数 | 完整形式为unsigned short int ,int 可以省略 | 全部位占满 | 8 |
long long | 无符号长长整数 | 完整形式为signed long long int ,singed 和int 可以省略 | C99特有 | 8 |
unsigned long long | 有符号长长整数 | 完整形式为unsigned long long int ,int 可以省略 | C99特有 | 8 |
char | 字符 | 实质是“小整数”(可能比短整数占用字节更少) | 分为char 、singed char 和unsigned char 。使用单引号标记常量,比如'A' (值为65) | 1 |
_Bool | 布尔型整数 | 实质是无符号整数 | 只能赋值0或1,赋值_Bool 类型变量为非零值会导致赋值为1 | 1 |
float | 单精度浮点数 | 4 | ||
double | 双精度浮点数 | 8 | ||
long double | 扩展精度浮点数 | 16 |
强制编辑器处理常量为长整数(十进制、八进制和十六进制),1135L
;强制处理为无符号,1135U
;混合使用,1135UL
,U
和L
顺序和大小写不重要。
C99中增加了ll
或者LL
后缀,强制long long int
型整数,可以与u
或U
连用。避免无符号和有符号整数混用,特别是无符号和有符号整数比较,会产生意想不到的后果。
强制编辑器处理常量为单精度浮点数,11.3f
,11.3
会被认为是double
型;强制为双精度,11.3L
或者11.3l
(这里看出使用L
更好,因为l
会被误认为数字1
)。
强制类型转化表达式的一般形式为:(int) floatNumber
。C语言把(type)
视为一元运算符,所以其等级高于二元运算符。
类型定义一般形式为:typedef int Newint;
,注意结尾的;
。区别与使用宏定义类型,函数体内定义的typdef
变量在函数体外无法使用,而宏可以作用于任何对应位置。
sizeof
运算符一般形式为:sizeof(type)
,比如sizeof(long int)
计算int
类型占用多少个字节。sizeof
表达式的类型是size_t
(无符号整数),所以安全的方法是强制转换为unsigned long
型,比如(unsigned long) sizeof(int)
。括号不是强制需要,加上括号防止因为优先级不同而引起歧义。可以应用与常量、变量或者表达式。
数组索引从0开始;数组按行存储。
数组长度:
数组长度可以是任何整数常量或整数常量表达式,比如testLen + 1;
,testLen
之前声明为整数常量(>-1)。
数组长度之后可能会变,所以可以使用宏定义一维数组的长度,比如#define LEN 5
。
确定数组长度,可以联合使用宏和sizeof
,比如#define SIZE ((int) (sizeof(a) / sizeof(a[0])))
。
初始化:
声明每一个元素的数值,比如int testArray[5] = {1, 2, 3, 4, 5};
。这种情况可以忽略数组长度,比如int testArray[] = {1, 2, 3, 4, 5}
;。
声明部分元素,比如int testArray[5] = {1, 2, 3};,4号与5号元素默认为0。<span style="color: green">**C99**</span>提供元素下标初始化,比如:
int testArray[5] = {[0] = 1, [1] = 2, [2] = 3};;或者混用,比如int testArray[5] = {1, 2, [2] = 3};
;甚至自动判断长度,比如int testArray[] = {[0] = 1, 2, [2] = 3, a[4] = 0};
,编译器根据最大元素序号,制定数组长度为5。使用元素下标初始化,尽量按照下标序号从大到小初始化,否则可能引起后面元素覆盖前面元素。
声明全部元素为0,比如int testArray[5] = {0}
;。
初始化:
声明每一个元素或者部分元素,其余未声明元素为0。C99同样提供了下标初始化。比如:int testArray[2][2] = {[0][0] = 0, [1][1] = 1};
。
声明全部元素为0,比如int testArray[5][10] = {0};
。
C99允许声明变长数组。但是,变长数组的声明和初始化不能在一条语句中。正确的做法是:先声明变长数组,之后初始化。比如:
int size;
printf("Enter size of array: ");
scanf("%d", &size);
int square[size];
/* initial square */
for (int i = 0; i < size; i++) {
square[i] = 0;
}
字符串长度:
\0
(对应ASCII码为0)。使用<string.h>
头文件时,str()
函数返回输入字符串中,第一个空字符之前的字符数。初始化:
第一种储存为字符数组。char a[6] = "hello";
合法,最后一个是空字符;char a[6] = "hell";
合法,最后两个是空字符;省略长度,比如char a[] = "hello";
合法,最后一个是空字符,自动分配6个字符空间;char a[5] = "hello";
对于字符串非法,对于字符数组合法。
第二种储存为字符指针。char *b = "hello";
合法,声明字符指针;声明字符指针,将其指向已有字符变量、字符串字面量或动态分配字符串,合法;已有字符指针重新指向其他字符串,合法;声明字符指针,却不初始化,非法;声明字符指针,却不初始化,而且修改或赋值指向的字符,非法。
字符数组可以修改元素,比如a[0] = 'l';
;字符指针指向的字符串不能修改,应该杜绝*b = ’l’;之类的字符指针赋值,除非保证b
是一个字符数组;对于操作字符串的函数,如果形参为字符指针,允许修改其指向的内容,比如*b = 'l';
,因为默认(也只允许)传入参数是一个字符数组,而不是一个字符串字面量。
初始化:
char *a[5] = {"hello", "it", "is", "me", "!"};
;省略长度,char *a[] = {"hello", "it", "is", "me", "!"};
合法;char *a[5] = {"hello", "it", "is", "me"};
合法,最后一个是空指针(NULL
)。结构空间分配:
成员按照声明的顺序在内存依次排列。
第一个成员之前无间隙,因此方便指针指向结构。但成员之间或者最后一个成员之后,可能有间隙。
初始化:
结构中可以嵌套结构、联合、枚举、字符串等多种类型。
不需要初始化全部成员,剩下未初始化的成员使用0作为初始值,比如""
作为空字符串。
C99使用符合字面量,比如(struct test1) {.b = "test that", .c = 1};
。
struct test1 {
int a;
char b[10];
int c;
};
struct {
int a;
char b[10];
} p3;
/* p1.c is 0 */
struct test1 p1 = {1, "hello"};
/* c99 */
/* p1.a is 0 and p1.c is 3 */
struct test1 p2 = {.b = "world", 3};
/* c99 */
(struct test1){.c = 10};
typedef
定义,总结如下:typedef struct {
int ele1;
char ele2;
} Test1;
typedef union {
int ele1;
double ele2;
} Test2;
typedef enum {CIRCLE, RECTANGLE} Test3;
int main(void)
{
Test1 p1 = {.ele1 = 1};
Test2 p2 = {.ele2 = 0.2};
Test3 p3 = CIRCLE;
return 0;
}
联合空间分配:
初始化:
union test1 {
int a;
char b[10];
};
union {
int a;
char b[10];
} p3;
union test1 p1 = {0};
/* c99 */
union test1 p2 = {.b = "world"};
/* c99 */
(union test1){.a = 10};
union test {
int a;
struct test1 {
int b1;
char b2[10];
} b;
struct test2 {
int c1;
int c2;
} c;
} t1;
t1.b.b1 = 10;
/* t.c.c1 is 10 */
printf("%d \n", t1.c.c1);
初始化:
enum {THIS = 2, THAT = 10, THESE = 2,} test1
;。enum {FIRST, SECOND} p1;
enum test1 {FIRST, SECOND};
enum test1 = SECOND;
参考C语言指针记录。
函数不能返回数组,也不能返回两个数。
函数返回类型可以是void
,形参可以是void
。void
类型函数没有返回值,可以写一条表达式为空的return
语句,比如return
;。
每个形参必须单独声明类型,使用,
连接;
使用void
丢弃函数返回值,比如printf()
函数返回显示的字符数目,强制丢弃返回数值写为:(void) printf("Drop the return value.");
。
main
函数返回值:return
语句;exit()
函数(位于<stdlib.h>
库)。任何函数调用exit()
函数,都会导致程序终止;只有main
函数调用return
语句,程序才终止。C99规定:非void
类型函数必须指定返回类型,不能缺省。
函数声明的形式为int thisFun(double para1, int para2);
。C99规定:函数在调用前,必须事先声明或者定义。调用前声明有很多好处,比如避免实际参数的默认转换等。
/* 一维数组长度不是必须 */
int oneArrayFun(int a[], int len) {
...
}
/* 函数声明 */
int oneArrayFun(int a[], int len);
/* 省略形参声明 */
int oneArrayFun(int [], int);
/* 实际调用 */
oneArrayFun(testArry, testLen);
对于一维数组类型的参数,“长度”的实际参数可以比形式参数小,但不能大。
函数可以改变一维数组形参的值,并在实参中体现。
C89定义形参为多维数组的函数,只能省略第一维长度,其余维度必须声明(即常量),普遍形式为:
/* 比如二维数组,行不是必须,但必须制定列 */
#define COL 10
int twoArrayFun(int a[][COL], int row) {
...
}
/* 函数声明 */
int twoArrayFun(int a[][COL], int row);
/* 省略形参声明 */
int twoArrayFun(int [][COL], int);
/* 实际调用 */
twoArrayFun(testArry, testRow);
/* 一维VLA */
int oneArrayFun(int len, int a[len]) {
...
}
/* 一维VLA声明的各种形式 */
int oneArrayFun(int len, int a[len]);
int oneArrayFun(int len, int a[*]);
int oneArrayFun(int, int [*]);
int oneArrayFun(int len, int a[]);
int oneArrayFun(int, int []);
/* 实际调用 */
oneArrayFun(testArry, testLen);
/* 二维VLA */
int twoArrayFun(int row, int col, int a[row][col]) {
...
}
/* 二维VLA声明的各种形式 */
int twoArrayFun(int row, int col, int a[row][col]);
int twoArrayFun(int row, int col, int a[*][*]);
int twoArrayFun(int row, int col, int a[][col]);
int twoArrayFun(int row, int col, int a[][*]);
static
,但只能用于第一维。好处是编译器更快访问数组,比如:/* 一维数组长度至少为5 */
int oneArrayFun(int a[static 5], int len) {
...
}
const
修饰。#include <stdio.h>
int oneArray(int a[], int len);
int main(void)
{
printf("Sum is %d. \n", oneArray((int []){1, 2, 3, 4, 5}, 5));
int i = 1, j = 2, k = 3;
printf("Sum is %d. \n", oneArray((int [5]){i, j, k}, 5));
printf("Sum is %d. \n", oneArray((int [5]){1, 3, 5, 7}, 4));
printf("Sum is %d. \n",
constOneArray((const int [5]){1, 1, 1, 1}, 3));
return 0;
}
int oneArray(int a[], int len) {
int sum = 0;
for (int i = 0; i < len; i ++) {
sum += a[i];
}
return sum;
}
int constOneArray(const int a[], int len) {
int sum = 0;
for (int i = 0; i < len; i ++) {
sum += a[i];
}
return sum;
}
图片取自参考资料2。
操作符有两个性质:结合方向和优先级。结合方向决定操作符的执行对象,比如多个同等操作符;而优先级决定操作符的结合方式,通俗来讲即谁和谁结合在一起。但是,C没有规定表达式运算的先后顺序。比如对于二元操作符+
,a = i + i++;
,由编译器决定是i
还是i++
先执行。
操作符/
和%
用于负整数操作,结果由编译器决定。C99中/
和%
操作负数,返回最靠近0的结果。
运算符&&
和||
(从左向右结合),两侧的两个表达式有运算顺序,先左后右。有可能右侧表达式没有计算,因此不要在右侧放入有副作用的表达式。
条件表达式i > 0 ? i : f;
,如果i
和f
是整数型和浮点型,即使条件判定为真,表达式的值为浮点型。
switch
语句最后一个分支,添加break;语句。原因是防止之后修改程序,需要再添加判断条件时,遗漏
break;语句。C语言的switch
语句不能判断范围,但适合替代多个OR
连接的判断语句。
逗号表达式中,表达式1, 表达式2
,表达式1
先计算之后丢弃其值,之后计算表达式2
。因此,表达式1
必须有副作用。
#define LOWER 0
定义常量的语句之后,没有分号;
。
break;
只能跳出一层循环。
continue;
。有意思的应用场景,结合if
语句和continue;
语句,在循环中条件性忽略一些语句。
EOF
是文档结束的标志(End of File),在<stdio.h>
定义为一个整数-1
,代码如下:
#ifndef EOF
# define EOF (-1)
#endif
用单引号''
标记的字符,对应的是ASCII的数字值。
for
循环声明需要有主体;如无,在for
语句后添加;
作“无效声明(null statement)”。为了避免误解,;
单独占一行,或者使用{}
代替单独一行的;
。
顺序点(sequence point)
&&
、||
和comma operators,左边和右边表达式之间。
三元条件操作符?:
,在条件判断表达式与第二(第三)表达式之间。
完整表达式结束,包括赋值、return
语句、if
/switch
/while
/do-while
条件表达式判断结束和for
三个表示式。
函数的所有参数赋值和函数第一条语句执行之前(见后举例)。
变量初始化语句结束,比如int a = 1;
。如果多个变量初始化(,
分割),则在每一个,
结束处,比如 int a = 1, b = 2;
。
i++
与++i
大多数情况用于整数操作。
++i
马上自增,i++
自增则在两个相邻顺序点之间进行。表达式++i值为
i+1,表达式
i++值为i
。
表达式i++ == 0;
中,i
使用原始数值,在表达式结束后自增;相反,表达式++i == 0;
中,i
马上自增后与0比较。
对于表达式f(i++)
,传入的参数值为i
,但是在函数内部开始执行前,i
完成自增。这是因为在函数的所有参数赋值和函数第一条语句执行之前有一个顺序点。
在for
语句中,使用for(i = 0; i < 10; i++)
与for(i = 0; i < 10; ++i)
效果一样。
对于现代编译器,i++
和++i
的执行效率没有区别。所以写代码时,按照自认为最清楚的方式写。
未定义行为(undefined behavior)
一个表达式中既访问又修改同一个变量。
数组访问超过上下限。造成原因:很有可能是忘记数组从0开始索引。
C89中,非void
类型函数,执行到末尾,没有执行return
语句。程序调用此类函数,会出现未定义行为。C99中,不合法。
副作用(side effect)
所有赋值运算、i++
和++i
都有副作用,即改变原始变量的值。
赋值运算的值是赋值操作后右侧的值,并且将其强制转换为左侧值类型;赋值运算左侧必须为“左值(lvalue)”。
在同一个表达式,即访问某个变量,同时又修改这个变量,会造成**“未定义行为”**。有副作用的操作,会带来隐晦未定义行为。未定义行为会随着不同的编译器,而产生不同的结果。其危险性不仅在于阻碍跨平台使用,而且也会有程序运行失败或者得到意想不到结果。建议:不在一个表达式中即访问又修改同一个变量。一些典型的未定义行为的例子:
# can not decide whether "++", "=", or "+" is the first
a = i + i++;
i = i++;
a[i] = i++;
# "," is not comma operator
printf("%d %d\n", ++i, i);
#include <stdio.h>
printf("%3.0f %6.1f\n", fahr, celsius);
%m.pX
:格式串模板,m
表示要显示的最小字段宽度,p
表示精度,X
表示类型。
对于 printf()
函数:
%d
:十进制整数。
%1d
一个十进制整数,配合scanf()
实现单个整数输入操作。
%6d
:十进制整数,至少6位宽,右对齐;如果数字位数超过6位,则全部显示。
%-6d
:同上,区别左对齐。
%-6.3d
:同上,如果数字位数少于3位,左侧加0。比如,printf(".2d%", 5)
的输出为05
。
%u
:无符号十进制数。
%o
:无符号八进制数。
%x
:无符号十六进制数。
没有标准方法打印负八进制和负十六进制数。
%h
:短整数前缀。
%l
:长整数前缀。
%ll
:长长整数前缀,C99特有。
%h
、%l
、%ll
与d
、u
、o
和x
连用,比如ho
表示无符号短八进制数。
%f
:浮点数,默认小数位为6位。
%6f
:浮点数,至少6位宽。
%6.0f
:浮点树,至少6位宽,无小数点而且无小数位。
%.2f
:浮点数,小数点后两位。
%6.2f
:浮点数,至少6位宽,小数点后两位。
%e
:科学计数。
%g
:科学计数或者浮点数。
%l
:double
型前缀。
%l
只在scanf()
中使用,在printf()
使用%f
和%e
(或者%g
)表示float
型和double
型。
C99中允许在printf()
使用%lf
、%le
和%lg
,但是l
不起作用。
%L
:long double
型前缀。
%l
和%L
与e
、f
、g
连用。
%c
:字符。
%s
:字符串。
%%
:%
自身。
对于scanf()
函数:
格式串中出现空白字符(空格、水平或者垂直制表符、换页符、换行符),数量无关紧要,因为可以匹配任意数量(包括0个)空白字符。
不要输把空白字符(比如"%d "
)放入格式串的结尾。原因:scanf()
会挂起,直到出现不能匹配的空白字符。
在for
语句中声明的计数变量,只能在for循环中使用。比如 for(int i = 0; i < 10; i++){...}
,变量i
只能在循环内起作用。
新增加表示布尔数值的头文件<stdbool.h>
,使用方法:
#include <stdbool.h>
int main(void)
{
bool trueVal = true, falseVal = false;
bool a1[10] = {false};
bool a2[10][10] = {false};
return 0;
}
typedef
定义占用特定位数(是“比特数”,不是“字节数”)的整数,比如:#include <stdint.h>
int main(void)
{
typedef int32_t Int32;
return 0;
}
BW Kernighan and DM Ritchie: The C Programming Language (2nd Edition), 1988.
KN King: C Programming: A Modern Approach, 2nd Edition, 2008.
2015年4月30日