2013-07-30 20:57:45 +04:00
- - -
2022-08-01 10:46:50 +03:00
language : C
2013-08-12 20:53:40 +04:00
filename : learnc - cn . c
2013-07-30 20:57:45 +04:00
contributors :
2013-08-01 22:24:23 +04:00
- [ " Adam Bard " , " http://adambard.com/ " ]
2013-07-30 20:57:45 +04:00
translators :
2013-08-01 22:24:23 +04:00
- [ " Chenbo Li " , " http://binarythink.net/ " ]
2014-02-14 17:08:09 +04:00
- [ " Jakukyo Friel " , " http://weakish.github.io " ]
2013-08-01 22:24:23 +04:00
lang : zh - cn
2013-07-30 20:57:45 +04:00
- - -
C语言在今天仍然是高性能计算的主要选择 。
2014-02-14 17:08:09 +04:00
C大概是大多数程序员用到的最接近底层的语言了 , C语言原生的速度就很高了 , 但 是 别 忘 了 C的手动内存管理 , 它 会 让 你 将 性 能 发 挥 到 极 致 。
2013-07-30 20:57:45 +04:00
` ` ` c
2014-02-14 17:08:09 +04:00
// 单行注释以//开始。( 仅适用于C99或更新的版本。)
2013-07-30 20:57:45 +04:00
/*
2014-02-14 17:08:09 +04:00
多 行 注 释 是 这 个 样 子 的 。 ( C89也适用 。 )
2013-07-30 20:57:45 +04:00
*/
2014-02-14 17:08:09 +04:00
// 常数: #define 关键词
# define DAYS_IN_YEAR 365
// 以枚举的方式定义常数
enum days { SUN = 1 , MON , TUE , WED , THU , FRI , SAT } ;
// MON自动被定义为2, TUE被定义为3, 以此类推。
2013-07-30 20:57:45 +04:00
// 用#include来导入头文件
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
2014-02-14 17:08:09 +04:00
// <尖括号>间的文件名是C标准库的头文件。
// 标准库以外的头文件,使用双引号代替尖括号。
# include "my_header.h"
// 函数的签名可以事先在.h文件中定义,
// 也可以直接在.c文件的头部定义。
void function_1 ( char c ) ;
void function_2 ( void ) ;
2013-07-30 20:57:45 +04:00
2018-08-31 09:35:24 +03:00
// 如果函数调用在main()之后, 那么必须声明在main()之前
2014-02-14 17:08:09 +04:00
// 先声明一个函数原型
int add_two_ints ( int x1 , int x2 ) ; // 函数原型
// 你的程序的入口是一个返回值为整型的main函数
2013-07-30 20:57:45 +04:00
int main ( ) {
2014-02-14 17:08:09 +04:00
// 用printf打印到标准输出, 可以设定格式,
// %d 代表整数, \n 代表换行
printf ( " %d \n " , 0 ) ; // => 打印 0
2013-07-30 20:57:45 +04:00
// 所有的语句都要以分号结束
///////////////////////////////////////
// 类型
///////////////////////////////////////
// 在使用变量之前我们必须先声明它们。
// 变量在声明时需要指明其类型,而类型能够告诉系统这个变量所占用的空间
// int型( 整型) 变量一般占用4个字节
int x_int = 0 ;
// short型( 短整型) 变量一般占用2个字节
short x_short = 0 ;
// char型( 字符型) 变量会占用1个字节
char x_char = 0 ;
char y_char = ' y ' ; // 字符变量的字面值需要用单引号包住
// long型( 长整型) 一般需要4个字节到8个字节; 而long long型则至少需要8个字节( 64位)
long x_long = 0 ;
long long x_long_long = 0 ;
// float一般是用32位表示的浮点数字
float x_float = 0.0 ;
// double一般是用64位表示的浮点数字
double x_double = 0.0 ;
// 整数类型也可以有无符号的类型表示。这样这些变量就无法表示负数
// 但是无符号整数所能表示的范围就可以比原来的整数大一些
unsigned short ux_short ;
unsigned int ux_int ;
unsigned long long ux_long_long ;
2014-02-14 17:08:09 +04:00
// 单引号内的字符是机器的字符集中的整数。
' 0 ' // => 在ASCII字符集中是48
' A ' // => 在ASCII字符集中是65
2013-07-30 20:57:45 +04:00
// char类型一定会占用1个字节, 但是其他的类型却会因具体机器的不同而各异
// sizeof(T) 可以返回T类型在运行的机器上占用多少个字节
// 这样你的代码就可以在各处正确运行了
2014-02-14 17:08:09 +04:00
// sizeof(obj)返回表达式(变量、字面量等)的尺寸
printf ( " %zu \n " , sizeof ( int ) ) ; // => 4 (大多数的机器字长为4)
// 如果`sizeof`的参数是一个表达式, 那么这个参数不会被演算( VLA例外, 见下)
// 它产生的值是编译期的常数
int a = 1 ;
// size_t是一个无符号整型, 表示对象的尺寸, 至少2个字节
size_t size = sizeof ( a + + ) ; // a++ 不会被演算
printf ( " sizeof(a++) = %zu where a = %d \n " , size , a ) ;
// 打印 "sizeof(a++) = 4 where a = 1" ( 在32位架构上)
// 数组必须要被初始化为具体的长度
2013-07-30 20:57:45 +04:00
char my_char_array [ 20 ] ; // 这个数组占据 1 * 20 = 20 个字节
int my_int_array [ 20 ] ; // 这个数组占据 4 * 20 = 80 个字节
// (这里我们假设字长为4)
// 可以用下面的方法把数组初始化为0:
char my_array [ 20 ] = { 0 } ;
2014-02-14 17:08:09 +04:00
// 索引数组和其他语言类似 -- 好吧, 其实是其他的语言像C
2013-07-30 20:57:45 +04:00
my_array [ 0 ] ; // => 0
// 数组是可变的,其实就是内存的映射!
my_array [ 1 ] = 2 ;
printf ( " %d \n " , my_array [ 1 ] ) ; // => 2
2014-02-14 17:08:09 +04:00
// 在C99 ( C11中是可选特性) , 变长数组( VLA) 也可以声明长度。
// 其长度不用是编译期常量。
printf ( " Enter the array size: " ) ; // 询问用户数组长度
char buf [ 0x100 ] ;
fgets ( buf , sizeof buf , stdin ) ;
2020-06-13 15:20:20 +03:00
// strtoul 将字符串解析为无符号整数
2014-02-14 17:08:09 +04:00
size_t size = strtoul ( buf , NULL , 10 ) ;
int var_length_array [ size ] ; // 声明VLA
printf ( " sizeof array = %zu \n " , sizeof var_length_array ) ;
// 上述程序可能的输出为:
// > Enter the array size: 10
// > sizeof array = 40
2013-07-30 20:57:45 +04:00
// 字符串就是以 NUL (0x00) 这个字符结尾的字符数组,
2014-02-14 17:08:09 +04:00
// NUL可以用'\0'来表示.
// (在字符串字面量中我们不必输入这个字符,编译器会自动添加的)
2013-07-30 20:57:45 +04:00
char a_string [ 20 ] = " This is a string " ;
printf ( " %s \n " , a_string ) ; // %s 可以对字符串进行格式化
/*
也 许 你 会 注 意 到 a_string 实 际 上 只 有 16 个 字 节 长 .
第 17 个 字 节 是 一 个 空 字 符 ( NUL )
2014-02-14 17:08:09 +04:00
而 第 18 , 19 和 20 个 字 符 的 值 是 未 定 义 。
2013-07-30 20:57:45 +04:00
*/
printf ( " %d \n " , a_string [ 16 ] ) ; // => 0
2014-02-14 17:08:09 +04:00
// byte #17值为0( 18, 19, 20同样为0)
// 单引号间的字符是字符字面量
// 它的类型是`int`,而 *不是* `char`
// (由于历史原因)
int cha = ' a ' ; // 合法
char chb = ' a ' ; // 同样合法 (隐式类型转换
// 多维数组
int multi_array [ 2 ] [ 5 ] = {
{ 1 , 2 , 3 , 4 , 5 } ,
{ 6 , 7 , 8 , 9 , 0 }
}
// 获取元素
int array_int = multi_array [ 0 ] [ 2 ] ; // => 3
2013-07-30 20:57:45 +04:00
///////////////////////////////////////
// 操作符
///////////////////////////////////////
2014-02-14 17:08:09 +04:00
// 多个变量声明的简写
int i1 = 1 , i2 = 2 ;
2013-07-30 20:57:45 +04:00
float f1 = 1.0 , f2 = 2.0 ;
2014-02-14 17:08:09 +04:00
int a , b , c ;
a = b = c = 0 ;
// 算数运算直截了当
2013-07-30 20:57:45 +04:00
i1 + i2 ; // => 3
i2 - i1 ; // => 1
i2 * i1 ; // => 2
2014-02-14 17:08:09 +04:00
i1 / i2 ; // => 0 (0.5,但会被化整为 0)
2013-07-30 20:57:45 +04:00
f1 / f2 ; // => 0.5, 也许会有很小的误差
2014-02-14 17:08:09 +04:00
// 浮点数和浮点数运算都是近似值
2013-07-30 20:57:45 +04:00
// 取余运算
11 % 3 ; // => 2
2014-02-14 17:08:09 +04:00
// 你多半会觉得比较操作符很熟悉, 不过C中没有布尔类型
2013-07-30 20:57:45 +04:00
// 而是用整形替代
2014-02-14 17:08:09 +04:00
// (C99中有_Bool或bool。)
// 0为假, 其他均为真. (比较操作符的返回值总是返回0或1)
2013-07-30 20:57:45 +04:00
3 = = 2 ; // => 0 (false)
3 ! = 2 ; // => 1 (true)
3 > 2 ; // => 1
3 < 2 ; // => 0
2 < = 2 ; // => 1
2 > = 2 ; // => 1
2014-02-14 17:08:09 +04:00
// C不是Python —— 连续比较不合法
int a = 1 ;
// 错误
int between_0_and_2 = 0 < a < 2 ;
// 正确
int between_0_and_2 = 0 < a & & a < 2 ;
// 逻辑运算符适用于整数
2013-07-30 20:57:45 +04:00
! 3 ; // => 0 (非)
! 0 ; // => 1
1 & & 1 ; // => 1 (且)
0 & & 1 ; // => 0
0 | | 1 ; // => 1 (或)
0 | | 0 ; // => 0
2014-02-14 17:08:09 +04:00
// 条件表达式 ( ? : )
int a = 5 ;
int b = 10 ;
int z ;
z = ( a > b ) ? a : b ; // 10 “若a > b返回a, 否则返回b。”
// 增、减
char * s = " iLoveC "
int j = 0 ;
s [ j + + ] ; // "i" 返回s的第j项, 然后增加j的值。
j = 0 ;
s [ + + j ] ; // => "L" 增加j的值, 然后返回s的第j项。
// j-- 和 --j 同理
2013-07-30 20:57:45 +04:00
// 位运算
~ 0x0F ; // => 0xF0 (取反)
0x0F & 0xF0 ; // => 0x00 (和)
0x0F | 0xF0 ; // => 0xFF (或)
0x04 ^ 0x0F ; // => 0x0B (异或)
0x01 < < 1 ; // => 0x02 (左移1位)
0x02 > > 1 ; // => 0x01 (右移1位)
2014-02-14 17:08:09 +04:00
// 对有符号整数进行移位操作要小心 —— 以下未定义:
// 有符号整数位移至符号位 int a = 1 << 32
// 左移位一个负数 int a = -1 << 2
// 移位超过或等于该类型数值的长度
// int a = 1 << 32; // 假定int32位
2013-07-30 20:57:45 +04:00
///////////////////////////////////////
// 控制结构
///////////////////////////////////////
if ( 0 ) {
printf ( " I am never run \n " ) ;
} else if ( 0 ) {
printf ( " I am also never run \n " ) ;
} else {
printf ( " I print \n " ) ;
}
// While循环
int ii = 0 ;
2014-02-14 17:08:09 +04:00
while ( ii < 10 ) { // 任何非0的值均为真
2013-07-30 20:57:45 +04:00
printf ( " %d, " , ii + + ) ; // ii++ 在取值过后自增
2014-02-14 17:08:09 +04:00
} // => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "
2013-07-30 20:57:45 +04:00
printf ( " \n " ) ;
int kk = 0 ;
do {
printf ( " %d, " , kk ) ;
2014-02-14 17:08:09 +04:00
} while ( + + kk < 10 ) ; // ++kk 先自增,再被取值
// => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "
2013-07-30 20:57:45 +04:00
printf ( " \n " ) ;
// For 循环
int jj ;
for ( jj = 0 ; jj < 10 ; jj + + ) {
printf ( " %d, " , jj ) ;
2014-02-14 17:08:09 +04:00
} // => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "
2013-07-30 20:57:45 +04:00
printf ( " \n " ) ;
2014-02-14 17:08:09 +04:00
// *****注意*****:
// 循环和函数必须有主体部分,如果不需要主体部分:
int i ;
for ( i = 0 ; i < = 5 ; i + + ) {
; // 使用分号表达主体( null语句)
}
// 多重分支: switch()
switch ( some_integral_expression ) {
case 0 : // 标签必须是整数常量表达式
do_stuff ( ) ;
break ; // 如果不使用break, 控制结构会继续执行下面的标签
case 1 :
do_something_else ( ) ;
break ;
default :
// 假设 `some_integral_expression` 不匹配任何标签
fputs ( " error! \n " , stderr ) ;
exit ( - 1 ) ;
break ;
}
2013-07-30 20:57:45 +04:00
///////////////////////////////////////
// 类型转换
///////////////////////////////////////
// 在C中每个变量都有类型, 你可以将变量的类型进行转换
2014-02-14 17:08:09 +04:00
// (有一定限制)
2013-07-30 20:57:45 +04:00
2014-02-14 17:08:09 +04:00
int x_hex = 0x01 ; // 可以用16进制字面量赋值
2013-07-30 20:57:45 +04:00
// 在类型转换时,数字本身的值会被保留下来
2014-02-14 17:08:09 +04:00
printf ( " %d \n " , x_hex ) ; // => 打印 1
printf ( " %d \n " , ( short ) x_hex ) ; // => 打印 1
printf ( " %d \n " , ( char ) x_hex ) ; // => 打印 1
2013-07-30 20:57:45 +04:00
// 类型转换时可能会造成溢出,而且不会抛出警告
2014-02-14 17:08:09 +04:00
printf ( " %d \n " , ( char ) 257 ) ; // => 1 (char的最大值为255, 假定char为8位长)
// 使用<limits.h>提供的CHAR_MAX、SCHAR_MAX和UCHAR_MAX宏可以确定`char`、`signed_char`和`unisigned char`的最大值。
2013-07-30 20:57:45 +04:00
// 整数型和浮点型可以互相转换
2014-02-14 17:08:09 +04:00
printf ( " %f \n " , ( float ) 100 ) ; // %f 格式化单精度浮点
printf ( " %lf \n " , ( double ) 100 ) ; // %lf 格式化双精度浮点
2013-07-30 20:57:45 +04:00
printf ( " %d \n " , ( char ) 100.0 ) ;
///////////////////////////////////////
// 指针
///////////////////////////////////////
// 指针变量是用来储存内存地址的变量
2014-02-15 17:37:58 +04:00
// 指针变量的声明也会告诉它所指向的数据的类型
// 你可以使用得到你的变量的地址,并把它们搞乱,;-)
2013-07-30 20:57:45 +04:00
int x = 0 ;
printf ( " %p \n " , & x ) ; // 用 & 来获取变量的地址
2014-02-14 17:08:09 +04:00
// (%p 格式化一个类型为 void *的指针)
// => 打印某个内存地址
2013-07-30 20:57:45 +04:00
2014-02-14 17:08:09 +04:00
// 指针类型在声明中以*开头
int * px , not_a_pointer ; // px是一个指向int型的指针
2013-07-30 20:57:45 +04:00
px = & x ; // 把x的地址保存到px中
2014-02-14 17:08:09 +04:00
printf ( " %p \n " , ( void * ) px ) ; // => 输出内存中的某个地址
printf ( " %zu, %zu \n " , sizeof ( px ) , sizeof ( not_a_pointer ) ) ;
// => 在64位系统上打印“8, 4”。
2013-07-30 20:57:45 +04:00
2014-02-14 17:08:09 +04:00
// 要得到某个指针指向的内容的值,可以在指针前加一个*来取得(取消引用)
// 注意: 是的,这可能让人困惑,'*'在用来声明一个指针的同时取消引用它。
2013-07-30 20:57:45 +04:00
printf ( " %d \n " , * px ) ; // => 输出 0, 即x的值
// 你也可以改变指针所指向的值
2014-02-14 17:08:09 +04:00
// 此时你需要取消引用上添加括号,因为++比*的优先级更高
( * px ) + + ; // 把px所指向的值增加1
2013-07-30 20:57:45 +04:00
printf ( " %d \n " , * px ) ; // => 输出 1
printf ( " %d \n " , x ) ; // => 输出 1
2014-02-14 17:08:09 +04:00
// 数组是分配一系列连续空间的常用方式
int x_array [ 20 ] ;
2013-07-30 20:57:45 +04:00
int xx ;
for ( xx = 0 ; xx < 20 ; xx + + ) {
x_array [ xx ] = 20 - xx ;
} // 初始化 x_array 为 20, 19, 18,... 2, 1
2014-02-14 17:08:09 +04:00
// 声明一个整型的指针, 并初始化为指向x_array
2013-07-30 20:57:45 +04:00
int * x_ptr = x_array ;
// x_ptr现在指向了数组的第一个元素(即整数20).
2014-02-14 17:08:09 +04:00
// 这是因为数组通常衰减为指向它们的第一个元素的指针。
// 例如,当一个数组被传递给一个函数或者绑定到一个指针时,
//它衰减为(隐式转化为)一个指针。
// 例外: 当数组是`&`操作符的参数:
int arr [ 10 ] ;
int ( * ptr_to_arr ) [ 10 ] = & arr ; // &arr的类型不是`int *`!
// 它的类型是指向数组的指针( 数组由10个int组成)
// 或者当数组是字符串字面量(初始化字符数组)
char arr [ ] = " foobarbazquirk " ;
// 或者当它是`sizeof`或`alignof`操作符的参数时:
int arr [ 10 ] ;
int * ptr = arr ; // 等价于 int *ptr = &arr[0];
printf ( " %zu, %zu \n " , sizeof arr , sizeof ptr ) ; // 应该会输出"40, 4"或"40, 8"
2013-07-30 20:57:45 +04:00
// 指针的增减多少是依据它本身的类型而定的
2014-02-14 17:08:09 +04:00
// (这被称为指针算术)
printf ( " %d \n " , * ( x_ptr + 1 ) ) ; // => 打印 19
printf ( " %d \n " , x_array [ 1 ] ) ; // => 打印 19
2013-07-30 20:57:45 +04:00
// 你也可以通过标准库函数malloc来实现动态分配
2014-02-14 17:08:09 +04:00
// 这个函数接受一个代表容量的参数,参数类型为`size_t`
// 系统一般会从堆区分配指定容量字节大小的空间
// (在一些系统,例如嵌入式系统中这点不一定成立
// C标准对此未置一词。)
int * my_ptr = malloc ( sizeof ( * my_ptr ) * 20 ) ;
2013-07-30 20:57:45 +04:00
for ( xx = 0 ; xx < 20 ; xx + + ) {
2014-02-14 17:08:09 +04:00
* ( my_ptr + xx ) = 20 - xx ; // my_ptr[xx] = 20-xx
} // 初始化内存为 20, 19, 18, 17... 2, 1 (类型为int)
2013-07-30 20:57:45 +04:00
2014-02-14 17:08:09 +04:00
// 对未分配的内存进行取消引用会产生未定义的结果
2013-07-30 20:57:45 +04:00
printf ( " %d \n " , * ( my_ptr + 21 ) ) ; // => 谁知道会输出什么
2014-02-14 17:08:09 +04:00
// malloc分配的区域需要手动释放
2013-07-30 20:57:45 +04:00
// 否则没人能够再次使用这块内存,直到程序结束为止
free ( my_ptr ) ;
// 字符串通常是字符数组,但是经常用字符指针表示
2014-02-14 17:08:09 +04:00
// (它是指向数组的第一个元素的指针)
// 一个优良的实践是使用`const char *`来引用一个字符串字面量,
// 因为字符串字面量不应当被修改(即"foo"[0] = 'a'犯了大忌)
const char * my_str = " This is my very own string " ;
2013-07-30 20:57:45 +04:00
printf ( " %c \n " , * my_str ) ; // => 'T'
2014-02-14 17:08:09 +04:00
// 如果字符串是数组,(多半是用字符串字面量初始化的)
// 情况就不一样了,字符串位于可写的内存中
char foo [ ] = " foo " ;
foo [ 0 ] = ' a ' ; // 这是合法的, foo现在包含"aoo"
2013-07-30 20:57:45 +04:00
function_1 ( ) ;
} // main函数结束
///////////////////////////////////////
// 函数
///////////////////////////////////////
// 函数声明语法:
// <返回值类型> <函数名称>(<参数>)
int add_two_ints ( int x1 , int x2 ) {
return x1 + x2 ; // 用return来返回一个值
}
/*
2014-02-14 17:08:09 +04:00
函 数 是 按 值 传 递 的 。 当 调 用 一 个 函 数 的 时 候 , 传 递 给 函 数 的 参 数
是 原 有 值 的 拷 贝 ( 数 组 除 外 ) 。 你 在 函 数 内 对 参 数 所 进 行 的 操 作
不 会 改 变 该 参 数 原 有 的 值 。
但 是 你 可 以 通 过 指 针 来 传 递 引 用 , 这 样 函 数 就 可 以 更 改 值
2013-07-30 20:57:45 +04:00
例 子 : 字 符 串 本 身 翻 转
*/
// 类型为void的函数没有返回值
2014-02-14 17:08:09 +04:00
void str_reverse ( char * str_in ) {
2013-07-30 20:57:45 +04:00
char tmp ;
2014-02-14 17:08:09 +04:00
int ii = 0 ;
size_t len = strlen ( str_in ) ; // `strlen()`` 是C标准库函数
for ( ii = 0 ; ii < len / 2 ; ii + + ) {
2013-07-30 20:57:45 +04:00
tmp = str_in [ ii ] ;
str_in [ ii ] = str_in [ len - ii - 1 ] ; // 从倒数第ii个开始
str_in [ len - ii - 1 ] = tmp ;
}
}
/*
char c [ ] = " This is a test. " ;
str_reverse ( c ) ;
printf ( " %s \n " , c ) ; // => ".tset a si sihT"
*/
2014-02-14 17:08:09 +04:00
// 如果引用函数之外的变量, 必须使用extern关键字
int i = 0 ;
void testFunc ( ) {
extern int i ; // 使用外部变量 i
}
// 使用static确保external变量为源文件私有
static int i = 0 ; // 其他使用 testFunc()的文件无法访问变量i
void testFunc ( ) {
extern int i ;
}
//**你同样可以声明函数为static**
2013-07-30 20:57:45 +04:00
///////////////////////////////////////
// 用户自定义类型和结构
///////////////////////////////////////
// Typedefs可以创建类型别名
typedef int my_type ;
my_type my_type_var = 0 ;
2014-02-14 17:08:09 +04:00
// struct是数据的集合, 成员依序分配, 按照
// 编写的顺序
2013-07-30 20:57:45 +04:00
struct rectangle {
int width ;
int height ;
} ;
2014-02-14 17:08:09 +04:00
// 一般而言,以下断言不成立:
// sizeof(struct rectangle) == sizeof(int) + sizeof(int)
//这是因为structure成员之间可能存在潜在的间隙( 为了对齐) [1]
2013-07-30 20:57:45 +04:00
void function_1 ( ) {
struct rectangle my_rec ;
// 通过 . 来访问结构中的数据
my_rec . width = 10 ;
my_rec . height = 20 ;
// 你也可以声明指向结构体的指针
2014-02-14 17:08:09 +04:00
struct rectangle * my_rec_ptr = & my_rec ;
2013-07-30 20:57:45 +04:00
2014-02-14 17:08:09 +04:00
// 通过取消引用来改变结构体的成员...
2013-07-30 20:57:45 +04:00
( * my_rec_ptr ) . width = 30 ;
2014-02-14 17:08:09 +04:00
// ... 或者用 -> 操作符作为简写提高可读性
2013-07-30 20:57:45 +04:00
my_rec_ptr - > height = 10 ; // Same as (*my_rec_ptr).height = 10;
}
// 你也可以用typedef来给一个结构体起一个别名
typedef struct rectangle rect ;
int area ( rect r ) {
return r . width * r . height ;
}
2014-02-14 17:08:09 +04:00
// 如果struct较大, 你可以通过指针传递, 避免
// 复制整个struct。
int area ( const rect * r )
{
return r - > width * r - > height ;
}
2013-07-30 20:57:45 +04:00
///////////////////////////////////////
// 函数指针
///////////////////////////////////////
/*
在 运 行 时 , 函 数 本 身 也 被 存 放 到 某 块 内 存 区 域 当 中
2014-02-14 17:08:09 +04:00
函 数 指 针 就 像 其 他 指 针 一 样 ( 不 过 是 存 储 一 个 内 存 地 址 ) 但 却 可 以 被 用 来 直 接 调 用 函 数 ,
并 且 可 以 四 处 传 递 回 调 函 数
但 是 , 定 义 的 语 法 初 看 令 人 有 些 迷 惑
2013-07-30 20:57:45 +04:00
例 子 : 通 过 指 针 调 用 str_reverse
*/
2014-02-14 17:08:09 +04:00
void str_reverse_through_pointer ( char * str_in ) {
2013-07-30 20:57:45 +04:00
// 定义一个函数指针 f.
void ( * f ) ( char * ) ; // 签名一定要与目标函数相同
f = & str_reverse ; // 将函数的地址在运行时赋给指针
( * f ) ( str_in ) ; // 通过指针调用函数
// f(str_in); // 等价于这种调用方式
}
/*
2014-02-14 17:08:09 +04:00
只 要 函 数 签 名 是 正 确 的 , 任 何 时 候 都 能 将 任 何 函 数 赋 给 某 个 函 数 指 针
2013-07-30 20:57:45 +04:00
为 了 可 读 性 和 简 洁 性 , 函 数 指 针 经 常 和 typedef搭配使用 :
*/
typedef void ( * my_fnp_type ) ( char * ) ;
// 实际声明函数指针会这么用:
// ...
// my_fnp_type f;
2014-02-14 17:08:09 +04:00
// 特殊字符
' \a ' // bell
' \n ' // 换行
' \t ' // tab
' \v ' // vertical tab
' \f ' // formfeed
' \r ' // 回车
' \b ' // 退格
' \0 ' // null, 通常置于字符串的最后。
// hello\n\0. 按照惯例,\0用于标记字符串的末尾。
' \\ ' // 反斜杠
' \? ' // 问号
' \' ' // 单引号
' \" ' // 双引号
' \ xhh ' // 十六进制数字. 例子: '\xb' = vertical tab
2014-10-14 12:13:22 +04:00
' \ ooo ' // 八进制数字. 例子: '\013' = vertical tab
2014-02-14 17:08:09 +04:00
// 打印格式:
" %d " // 整数
" %3d " // 3位以上整数 (右对齐文本)
" %s " // 字符串
" %f " // float
" %ld " // long
" %3.2f " // 左3位以上、右2位以上十进制浮
" %7.4s " // (字符串同样适用)
" %c " // 字母
" %p " // 指针
" %x " // 十六进制
2014-10-14 12:13:22 +04:00
" %o " // 八进制
2014-02-14 17:08:09 +04:00
" %% " // 打印 %
///////////////////////////////////////
// 演算优先级
///////////////////////////////////////
//---------------------------------------------------//
// 操作符 | 组合 //
//---------------------------------------------------//
// () [] -> . | 从左到右 //
// ! ~ ++ -- + = *(type)sizeof | 从右到左 //
// * / % | 从左到右 //
// + - | 从左到右 //
// << >> | 从左到右 //
// < <= > >= | 从左到右 //
// == != | 从左到右 //
// & | 从左到右 //
// ^ | 从左到右 //
// | | 从左到右 //
// && | 从左到右 //
// || | 从左到右 //
// ?: | 从右到左 //
// = += -= *= /= %= &= ^= |= <<= >>= | 从右到左 //
// , | 从左到右 //
//---------------------------------------------------//
2013-07-30 20:57:45 +04:00
` ` `
# # 更多阅读
2014-02-14 17:08:09 +04:00
最 好 找 一 本 [ K & R , aka " The C Programming Language " , “ C程序设计语言 ” ] ( https : //en.wikipedia.org/wiki/The_C_Programming_Language)。它是关于C最重要的一本书, 由C的创作者撰写。不过需要留意的是它比较古老了, 因此有些不准确的地方。
2013-07-30 20:57:45 +04:00
2019-10-09 03:30:56 +03:00
另 一 个 比 较 好 的 资 源 是 [ Learn C the hard way ] ( http : //learncodethehardway.org/c/)
2013-07-30 20:57:45 +04:00
2014-02-14 17:08:09 +04:00
如 果 你 有 问 题 , 请 阅 读 [ compl . lang . c Frequently Asked Questions ] ( http : //c-faq.com/)。
2021-02-07 10:43:48 +03:00
使 用 合 适 的 空 格 、 缩 进 , 保 持 一 致 的 代 码 风 格 非 常 重 要 。 可 读 性 强 的 代 码 比 聪 明 的 代 码 、 高 速 的 代 码 更 重 要 。 可 以 参 考 下 [ Linux内核编码风格 ] ( https : //www.kernel.org/doc/Documentation/process/coding-style.rst)
2014-02-14 17:08:09 +04:00
。
2013-07-30 20:57:45 +04:00
除 了 这 些 , 多 多 Google吧
2014-02-14 17:08:09 +04:00
[ 1 ] http : //stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member