C语言笔记
表达式
表达式是运算符及其运算数的序列,它指定一个运算。
表达式求值可以产生结果(例如求值 2+2 产生结果 4 ,可能产生副效应(例如求值 printf(“%d”,4) 会将字符 ‘4’ 送到标准输出流),并可以指代对象或函数。
综述
- 值类别(左值、非左值对象、函数指代器)将表达式以其值分类
- 参数和子表达式的求值顺序指定会得到何种中间结果
声明
声明是一个引入一个或多个标识符到程序中,并指定其含义及属性的 C 语言构造。
声明可以出现在任何作用域中。每个声明以分号结束(类似语句),并由两个独立部分组成:
specifiers-and-qualifiers declarators-and-initializers ;
specifiers-and-qualifiers | – | 任意顺序的下列内容的空白符分隔列表
|
declarators-and-initializers | – | declarators 的逗号分隔列表(每个声明器提供附加类型信息及/或要声明的标识符)。 声明器可伴随初始化器。 enum 、 struct 和 union 声明可忽略 declarators ,这种情况下它们仅引入枚举常量和/或标签。
|
例如:
int a, *b=NULL; // “ int ”是类型指定符, // “ a ”是声明器 // “ *b ”是声明器 //而 NULL 是初始化器 const int *f(void); // “ int ”是类型指定符 // “ const ”是类型限定符 // “ *f(void) ”是声明器 enum COLOR {RED, GREEN, BLUE} c; // “ enum COLOR {RED, GREEN, BLUE} ”是类型指定符 // “ c ”是声明器
一个声明引入的每个标识符类型是通过 type specifier 所指定的类型及其 declarator 所应用的类型修饰决定的。
声明器
identifier | 此声明器引入的标识符。 |
( declarator )
|
任何可以放入括号中的声明器;引入指向数组或指向函数指针时要求这么做。 |
* qualifiers(可选) declarator
|
指针声明器:声明 S * cvr D 声明 D 为 cvr 限定指针,指向 S 所确定的类型。
|
noptr-declarator [ static(可选) qualifiers(可选) expression ]
noptr-declarator |
数组声明器:声明 S D[N] 声明 D 为有 N 个 S 所确定类型对象的数组。 noptr-declarator 是无括号指针声明器以外的其他任何声明器。
|
noptr-declarator ( parameters-or-identifiers )
|
函数声明器:声明 S D(params) 声明 D 为接收参数 params 并返回 S 的函数。 noptr-declarator 是无括号指针声明器以外的其他任何声明器。
|
struct C { int member; // “ int ”是类型指定符 // “ member ”是声明器 } obj, *pObj = &obj; // “ struct C { int member; } ”是类型指定符 // 声明器“ obj ”定义 struct C 类型的对象 // 声明器“ *pObj ”声明指向 struct C 的指针, // 初始化器“ = &obj ”提供该指针的初值 int a = 1, *p = NULL, f(void), (*pf)(double); // 类型指定符是“ int ” // 声明器“ a ”定义一个 int 类型对象 // 初始化器“=1”提供其初值 // 声明器“ *p ”定义一个指向 int 指针类型的对象 // 初始化器“ =NULL ”提供其初值 // 声明器“ f(void) ”声明接受 void 并返回 int 的函数 // 声明器“ (*pf)(double) ”定义一个指向 // 接受 double 并返回 int 的函数的指针类型对象 int (*(*foo)(double))[3] = NULL; // 类型指定符是“int” // 1. 声明器“ (*(*foo)(double))[3] ”是数组声明器: // 所声明类型是“ 3 个 int 的数组的 /嵌套声明器/ ” // 2. 嵌套声明器是“ *(*foo)(double)) ”,是指针声明器 // 所声明类型是“ /嵌套声明器/ 指向 3 个 int 的数组的指针” // 3. 嵌套声明器是“ (*foo)(double) ”,是一个函数声明器 // 所声明类型是“ /嵌套声明器/ 接受 double 并返回指向 3 个 int 的数组的指针的函数” // 4. 嵌套声明器是“ (*foo) ”,是一个(有括号,函数声明器所要求)指针声明器。 // 所声明类型是“ /嵌套声明器/ 指向接受 double 并返回指向 3 个 int 的数组的指针的函数的指针” // 5. 嵌套声明器是“ foo ”,是一个标识符。 // 该声明引入一个标识符“ foo ”,以指代一个对象,其类型为 // “指向接受 double 并返回指向 3 个 int 的数组的指针的函数的指针” // 初始化器“ = NULL ”提供此指针的初值。 // 若在用于声明符形式的表达式使用“foo”,则表达式类型将是int。 int x = (*(*foo)(1.2))[0];
每个不属于其他声明器一部分的声明器结尾是一个顺序点。
定义
定义是一个提供所有关于其所声明标识符信息的声明。
每个 enum 或 typedef 声明都是定义。
对于函数,包含函数体的声明即是函数定义:
int foo(double); // 声明 int foo(double x){ return x; } // 定义
对于对象,分配其存储的声明(自动或静态,但、非 extern )即是定义,而一个不分配存储的声明(外部声明)不是。
extern int n; // 声明 int n = 10; // 定义
对于结构体和联合体,指定其成员列表的声明是定义:
struct X; // 声明 struct X { int n; }; // 定义
初始化
对象声明可以通过名为初始化的步骤提供其初始值。
示例
#include <stdlib.h> int a[2]; //初始化a为{0, 0} int main(void) { int i; // 初始化 i 为不确定值 static int j; // 初始化 j 为 0 int k = 1; // 初始化 k 为 1 // 初始化 int x[3] 为 1,3,5 // 初始化 int* p 为 &x[0] int x[] = { 1, 3, 5 }, *p = x; // 初始化 w (二个结构体的数组)为 // { { {1,0,0}, 0}, { {2,0,0}, 0} } struct {int a[3], b;} w[] = {[0].a = {1}, [1].a[0] = 2}; // 函数调用表达式可用于局部变量初始化 char* ptr = malloc(10); free(ptr); // 错误:拥有静态存储期的对象要求常量初始化器 // static char* ptr = malloc(10); // 错误:不能初始化 VLA // int vla[n] = {0}; }
函数
函数是将一个标识符(函数名)关联到一条复合语句(函数体)的 C 语言构造。每个 C 程序都从 main 函数开始执行,也从它或者调用其他用户定义函数或库函数终止。
// 函数定义。 // 定义一个名为“ sum ”并拥有函数体“ { return x+y; } ”的函数 int sum(int x, int y) { return x + y; }
函数可以拥有零或更多个形参,它们为函数调用运算符的实参所初始化,并且以通过其 return 语句向其调用者返回一个值。
int n = sum(1, 2); // 参数 x 和 y 为实参 1 和 2 所初始化
函数体在函数定义中提供。每个函数必需且只需在程序中定义一次,除非该函数为 inline 。
不可以有嵌套函数(除了一些通过非标准的编译器扩展):每个函数定义必须出现在文件作用域,而且函数无法访问其来自其调用方的局部变量:
int main(void) // main函数定义 { int sum(int, int); // 函数声明(可以出现于任何作用域) int x = 1; // main 的局部变量 sum(1, 2); // 函数调用 // int sum(int a, int b) // 错误:不允许嵌套函数 // { // return a + b; // } } int sum(int a, int b) // 函数定义 { // return x + a + b; // 错误:不能在 sum 中访问 main 的 x return a + b; }
语句
语句是带顺序执行的 C 程序段。任何函数体都是一条复合语句,继而为语句或声明的序列:
int main(void) { // 复合语句的开始 int n = 1; // 声明(非语句) n = n+1; // 表达式语句 printf("n = %d\n", n); // 表达式语句 return 0; // 返回语句 } // 复合语句之结尾,函数体之结尾
语句有五种类型:
- 复合语句
- 表达式语句
- 选择语句
- 迭代语句
- 跳转语句
标号
任何语句都能有标号,通过在语句自身前提供一个跟随冒号的名称。
标识符 : 语句
|
goto 语句的目标。 |
case 常量表达式 : 语句
|
switch 语句的 case 标号。
|
default : 语句
|
switch 语句的默认标号。 |
任何语句(但非声明)可以前附任意数量的标号,每个都声明一个 标识符 为标号名,标号名必须在闭合的函数中唯一(换言之,标号名拥有 函数作用域)。
标号声明自身没有效果,不会以任何方式变更控制流,或修改跟随其后的语句的行为。
复合语句
复合语句,或称块,是花括号所包围的语句与声明的序列。
{
语句 |
声明…(可选) }
复合语句允许将一组声明和语句组合入一个单元,并将其任何在期待单个语句的场所使用(例如在 if 语句或迭代语句中):
if (expr) // if 语句的开始 { // 开始块 int n = 1; // 声明 printf("%d\n", n); // 表达式语句 } // 块结尾, if 语句结尾
每个复合语句引入其自身的块作用域。
拥有静态存储期的变量的初始化器,及 VLA 声明符在每次控制流经过这些声明时按顺序执行,就像它们是语句一样:
int main(void) { // 块的开始 { // 块的开始 puts("hello"); // 表达式语句 int n = printf("abc\n"); // 声明,打印 "abc\n" ,将 4 存入 n int a[n*printf("1\n")]; // 声明,打印 "1\n" ,分配 8*sizeof(int) printf("%zu\n", sizeof(a)); // 表达式语句 } // 块结束, n 与 a 的作用域结束 int n = 7; // n 可以重新使用 }
表达式语句
跟随分号的表达式是一条语句。
表达式(可选) ;
典型的 C 程序中大多数语句是表达式语句,例如赋值或函数调用。
无表达式的表达式语句被称作空语句。它通常用于提供空循环体给 for 或 while 循环。它亦能用于在复合语句尾部或声明之前携带一个标号:
puts("hello"); // 表达式语句 char *s; while (*s++ != '\0') ; // 空语句
选择语句
选择语句根据表达式的值,选择数条语句之一执行。
if ( 表达式 ) 语句
|
if 语句 |
if ( 表达式 ) 语句 else 语句
|
if 语句带 else 子句
|
switch ( 表达式 ) 语句
|
switch 语句 |
迭代语句
迭代语句重复执行一条语句。
while ( 表达式 ) 语句
|
while 循环 |
do 语句 while ( 表达式 ) ;
|
do-while 循环 |
for ( 初始化子句 ; 表达式(可选) ; 表达式(可选) ) 语句
|
for 循环 |
跳转语句
跳转语句无条件地转移控制流。
break ;
|
break 语句 |
continue ;
|
continue 语句 |
return 表达式(可选) ;
|
return 语句带可选的表达式 |
goto 标识符 ;
|
goto 语句 |