C语言的设计哲学之一: 程序员知道本身在干什么-没有安全带!程序员
值的类型并非值的内在本质, 而是取决于它被使用的方式数组
1.#include <stdio.h>在预处理器处理的时候把stdio.h中的源码读到当前文件中, 而后交给编译器安全
2.gets()函数会把输入的值的换行符转化为NULL字节, 来结束字符串; 遇到EOF或者发生错误返回NULL指针, 因此返回NULL要用ferror或feof检查是发生错误仍是遇到EOF网络
3.指针属性: 地址值和指针类型. 1)地址值表示指针所标识变量的首地址; 2)指针类型告诉编译器该怎样进行接下来的访问数据结构
4.标量就是指char、int、double和枚举型等数值类型, 以及指针; 相对地, 像数组、结构体和共用体这样的将多个标量进行组合的类型, 咱们称之为聚合类型(aggregate)(字符串是char类型的数组, 也就不是标量了); 而数组带下标选择的是单一元素, 若此元素为前者数值类型则是标量, 不然为聚合类型; 的一个注意区分与变量和常量间的区别(二者说的不是一个概念);使用八进制'\101'而不是数字0101来对char类型进行赋值能够很明显让人知道是字符串, 并且使用'\101'能够嵌入到字符串中dom
5.scanf("%d", &num); 执行时从标准输入读取, 前导空白将被跳过, printf("*%010.5d\n*", 100)-->* 00100*;第一位10表示总长度, 3表示最小数字位数(标志位:"+-0 #"), printf("*%10.2s*\n", "ABC")->"* AB*";printf使用%f输出double, 而scanf使用%lf输入double类型, 是由于printf时类型提高(短变长), float会被提高为double型, 而scanf向float和double类型中存储大不同, 因此要用lf;scanf使用全部格式码(除了%c以外)时, 输入值以前的空白(空格、制表符、换行符等)会被跳过, 值后面的空白表示该值的结束, 所以, 用%s格式码输入字符串时, 中间不能包含空白函数
6.pus()函数会在字符串结尾添加一个换行符, 与gets()相反测试
7.C语言有四种数据类型-整型、浮点型、指针和聚合类型(数组和结构等), 全部其余的类型都是从这四种基本类型的某种组合派生而来!无布尔类型和字符串类型this
8.整型包括字符、短整型、整型和长整型, 并且都分为有符号和无符号两种版本spa
9.break和continue只是打断最内层的循环, 不会影响到外层循环
10.goto语句, 必须定义goto到的语句, 而且在以后加上冒号":", next_do:...., 能够用goto跳出多层循环, goto next_do;
11.getchar()函数返回一个整型值, 一个缘由是EOF须要的位数比字符型值提供的要多, 若是长于字符型, 会被截取\377为EOF
12.变量属性做用域、连接属性、存储类型;整型常量是能最小字节存储就最小字节存储, 若加长存储能够用L等;字符常量类型老是int
13.sizeof(int)返回int类型所占字节, sizeof(arr)返回数组arr所占总字节; 而sizeof(a = b + 1)判断表达式长度并不须要对a求值, 因此没有对a进行任何赋值
14.不论++仍是--都是对变量的值的一份拷贝, 前缀在赋值以前增长变量的值, 后缀在复制以后增长变量的值, 操做符的结果不是被他们修改的变量, 而是变量值的拷贝, 认识这点很是重要; 如++a = 10;是错误的, ++优先级较高, 返回值, 值固然不能用于左操做数
15.逗号操做符, 将多个表达式分隔开来, 这些表达式自左向右逐个进行求值, 整个逗号表达式返回的值就是最后一个表达式的值
16.*p中, p表明内存中某个特定位置的地址, *操做符使机器指向那个位置; 做为左值的时候这个表达式指定要修改的位置, 做为右值的时候它就提取当前存储于这个位置的值.
17.操做符的优先级与结合性; 优先级决定了两个相邻的操做符哪一个先执行, 能够依次相邻比较找出最早执行的(好比 1 + 2 + 3 * 4); 结合性就是一串操做符是从左到右依次执行仍是从右到左依次执行; 优先级和结合性都与操做符有关, 而与变量或者值无关
18.没有标明存储位置, 不能做为左值
19.C根本不会对数组长度作检查, 即便索引超过数组长度也不会报错, 不论取值仍是赋值!int a[5]; a[6] = 10; printf("%d", a[6]); 但这种会出现未知结果!!由于下个内存地址谁知道被谁使用呢!
20.标准容许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较, 但不容许与指向数组第一个元素以前的那个内存位置的指针进行比较!
21.指针减去一个整数后, 运算结果产生的指针所指向的位置在数组第一个元素以前, 那么它也是非法的!加法则稍有不一样, 若是结果指针指向数组最后一个元素后面的那个内存位置还是合法(但不应和不能对这个指针执行间接访问操做), 不过再日后就不合法了!
22.未被提早声明的函数会被编译器认为参数正确, 而且返回值为整数
23.向函数传参时传递的都是拷贝的值, 也就是值传递, 可是传递数组的时候是传递的数组指针, 也就是引用地址传递.因此函数定义时的数组参数并不须要加长度, 只需标明是数组结构便可(加上[]).
24.使用递归的时候堆栈里会产生不少函数变量, 由于堆栈的特性, 因此会覆盖原函数变量, 等本函数执行完后pop出最上变量;
25.数组名是一个指针常量, 只有在两种场合下数组不用指针常量表示-数组名用做sizeof的参数-&数组名[1]
26.指针与数组名间能够随意转换与使用, 好比int arr[10]; int *p = arr + 2; 能够直接使用p[2];C的下标和指针的间接引用是同样的!当不明白的时候就作下转换!好比此时p[-1]能够转换为*(p - 1), 即p[1]; 甚至能够写2[p], 由于其至关于*(2 + p); 可是p[11]即便超出下标范围也不会被检查!
27.数组下标引用实际执行的就是间接访问!!下标引用实际上只是间接访问表达式的一种假装形式!!
28.完全理解用指针和多维数组的关系
29.int matrix[2][5], *p = matrix;是错误的, 由于matrix是一个指向整数类型的指针而不是指向一个数组(a[5])的指针
30.int (*p)[10]指向数组的指针 p是指向包含10个整型元素的数组的指针
31.二维数组作参数必须指定列数 void fun(int (*mat)[10])或者void fun(int mat[][10])
32.在多维数组的初始值列表中, 只有第一维的长度会被自动计算出来
33.strlen("abcd") - strlen("abcde") >= 0;这条语句永远为真, 由于strlen返回的类型为size_t, 即unsigned int类型, 两个unsigned int类型相减根据类型自动转换翻译的依旧是unsigned int类型
34.聚合数据类型是指可以同时存储超过一个的单独数据, C提供了两种类型的聚合数据类型, 数组和结构, 数组能够经过下标访问时由于数组元素的长度相同, 但结构的成员长度可能不一样, 结构变量属于标量类型;
35.结构不能包含类型也是这个结构的成员, 可是他的成员能够是一个指向这个结构的指针
36.注意结构变量的边界对齐, sizeof(结构变量)返回的值中包含告终构中浪费的内存空间
37.一个联合的全部成员都存储于同一个内存位置, 经过访问不一样类型的联合成员, 内存中相同的位组合能够被解释为不一样的东西.
union { float f; int i; } fi; fi.f = 3.1415926; printf("%d\n", fi.i);
38.
typedef struct{ char *name; short sex; short age; } stu, *stup;
*stup最好的理解方式为相似int *p中把int替换为(struct{...}), 声明一个指向struct类型的指针;
39.malloc、calloc、realloc和free维护一个可用内存池, 当一个程序另外须要一些内存就调用alloc系列函数从内存池中提取一块连续的内存, 并返回一个指向这块内存的指针, 若是内存池为空则返回NULL, 因此必须对alloc系列函数返回的值进行检查肯定非NULL, 返回的void * 类型的指针能够被转换为任何类型的指针, 能够制做动态大小的数组
void *malloc(size_t size);
void *calloc(size_t num_elements, sizet element_size);返回内存指针前把内存中的数值初始化为0
void realloc(void *ptr, size_t new_size);修改已经分配的内存块(ptr)的大小, 若是比原来大就将新加的内存添加到原来内存以后, 小则删减后面部分, 若是原先内存不能改变则新建立一块内存, 因此realloc以后就不能使用原来的指针, 应该使用realloc返回的指针
void free(void *pointer);
40.经典的使用malloc分配内存方法
alloc.h
#include <stdlib.h> #define malloc /*注意此处为空 不能直接调用malloc*/ #define MALLOC(num, type) (type *)alloc((num) * sizeof(type)) extern void *alloc(size_t size);
接口
#include <stdio.h> #include "alloc.h" #undef malloc void *alloc(size_t size) { void *new_mem; new_mem = malloc(size); if (new_mem == NULL) { exit(1); } return new_mem; }
实现
#include "alloc.h" void function() { int *new_memory; new_memory = MALLOC(25, int); }
41.内存释放一部分是不容许的, 好比想用free(p + 5)释放不被容许;动态分配的内存必须整块一块儿释放, 可是realloc函数能够缩小一块动态分配的内存, 有效地释放它尾部的部份内存
42.define时左边常量不容许出现空格 不然会被认为是后个语句, 注意后者替换时括号的使用 宏定义语句中能够包含运算符"#"(将一个宏的参数不要计算而是把变量名转换为字符串字面量, 如#define PRINTF_INT(x) printf(#x "=%d\n", x))或者"##"(链接符),#x 会被替换为字符串 "x"(注意带引号),能够这样使用 printf("name" "John")
好比
#define STR(x) #x int main(int argc, char** argv) { printf("%s\n", STR(It's a long string)); // 输出 It's a long str return 0; }
#define PHP_FUNCTION ZEND_FUNCTION #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name)) #define ZEND_FN(name) zif_##name #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS) #define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, \ zval *this_ptr, int return_value_used TSRMLS_DC PHP_FUNCTION(count); // 预处理器处理之后, PHP_FUCNTION(count);就展开为以下代码 void zif_count(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
43.define函数与普通函数的区别为, 宏定义能够用于任何类型, 好比#define MAX(a,b) ((a) > (b) ? (a) : (b)); #define ECHO(s) (ges(s), puts(s))、#define ECHO(s) {gets(s); puts(s);}#define指令只能是一行 若是是换行则须要在行末把换行符转义(即加"\"), 通常的都会用do while来代替if语句, 由于";"不能乱加, 不然在if不加花括号的状况下很容易打乱if结构
#define ALLOC_ZVAL(z) \ do { \ (z) = (zval*)emalloc(sizeof(zval_gc_info)); \ GC_ZVAL_INIT(z); \ } while (0)
44.预处理命令后都不须要";", 经常使用预处理命令#include(能够文件嵌套, 注意区分两种搜索顺序, '/usr/include路径或当前路径'), #define, #undef, #if, #elif, #else, #endif, #ifdef, #ifndef,(#ifdef, #ifndef都是以#endif结尾) #error(编译程序(预处理阶段), 只要遇到#error就会生成一个编译错误提示消息(#error后跟的错误信息, 不用双引号), 并中止编译), #line(从新设定行号和文件名, 即修改__FILE__、__LINE__, 之后的__FILE__、__LINE__也是从当前所置值开始计算 #line 24 "a.txt", 在把其余语言解析为C语言时经常使用), #pragma
45.预处理器定义的符号__FILE__, __LINE__, __DATE__, __TIME__, __STDC__(编译器遵循ANSIC为1, 不然为0)
46.perror(存在于stdio.h)将错误输出到stderr;exit(存在于stdlib.h)返回错误码给操做系统(Linux下用$?查看)
perror(char const *s)用来将上一个函数发生错误的缘由输出到标准设备(stderr) 参数s所指的字符串会先打印出, 后面再加上错误缘由字符串, 此错误缘由依照全局变量errno的值来决定要输出的字符串, 在库函数中有个errno变量, 每一个errno值对应着以字符串表示的错误类型, 当你调用"某些"函数出错时, 该函数已经从新设置了errno的值, perror函数只是将你输入的一些信息和如今的errno所对应的错误一块儿输出。
47.计算机拥有大量不一样设备, 不少都与I/O操做有关, CD-ROM驱动器, 软盘和硬盘驱动器, 网络链接, 通讯端口和视频适配器等都是这类设备, 每种设备具备不一样的特性和操做协议, 操做系统负责这些不一样设备的通讯细节, 并向程序员提供一个更为简单和统一的I/O接口.ANSI C进一步对I/O的概念进行了抽象, 就C程序而言, 全部的I/O操做只是简单的从程序移进或者移出字节的事情, 所以, 好不惊奇的是, 这种字节流被称为流(stream), 程序员只要关心建立正确的输出字节数据, 以及正确的届时从输入读取的字节数据, 特定I/O设备的细节对程序员是隐藏
48.绝大多数流失彻底缓冲的(fully buffered), 这意味着读取和写入是从一块被称为缓冲区(buffer)的内存区域来回复制数据, 从内存中复制数据是很是快的, 用于输出流的缓冲区只有当它写满时才会刷新(flush, 物理写入)到设备或者文件中, 一次性把写满的缓冲区写入和逐片把程序产生的输出分别写入相比效率更高, 相似, 输入缓冲区当它为空经过从设备或文件读取下一块较大的输入, 从新填充缓冲区; 使用标准输入或输出时, 这种缓冲可能会引发混淆, 因此只有操做系统判定他们交互的设备没有关联时才会进行彻底缓冲.一个常见的策略就是把标准输入和输出联系到一块儿《 就是当请求输入时同时刷新输出缓冲区, 这样, 在用户必须进行输入以前, 提示用户进行输入的信息和之前写入到输出缓冲区中的内容将出如今屏幕上。
47.可使用fflush(stdout/stdin)迫使缓冲区刷新, 无论是否已满;
原型: int fflush(FILE *stream); stdin刷新标准输入缓冲区, 把输入缓冲区里的东西丢弃【非标准】, stdout刷新标准输出缓冲区, 把输出缓冲区里的东西打印到标准输出设备上, printf后面加上fflush(stdout)可提升打印效率
返回: 返回值为0表示成功, 返回值为EOF表示错误
48.FILE是一个数据结构, 用来访问一个流, 每一个流都有一个FILE与它关联, 为了在流上执行操做, 能够调用一些合适的函数, 并向它们传递一个与这个流相关联的FILE参数, 流经过fopen函数打开(能够打开文件或设备), 为了打开一个流你必需要指定须要访问的文件或设备, 以及其访问方式, fopen会验证文件或设备是否存在, 并初始化返回FILE *结构, 系统必须为每一个ANSI C程序提供至少三个流(stdin, stdout, stderror), 他们都是一个指向FILE结构的指针(参考47, 48好好理解)
49.为每一个文件活动文件声明一个指针变量, 其类型为FILE *, 这个指针指向这个FILE结构, 当它处于活动状态时由流使用
50.IO函数以三种基本的形式处理数据, 单字符/字符串/二进制数据, 对于每一种数据都有一组特定的函数对它们进行处理 -- ungetc(int ch, stdin); 把字符压回标准输入, 下次读的时候相似栈先进后出
ungetc('a', stdin); ungetc('b', stdin); printf("%c\n", getchar()); printf("%c\n", getchar());
结果
b
a
51.标准流I/O不须要打开或者关闭
52.不论以何种方式打开(rwa), 数据只能从文件的尾部写入!!
53."a+"表示该文件打开用于更新, 而且流既容许读也容许写. 可是若是你已经从该文件读了一些数据, 那么向它写入数据以前, 你必须调用一个文件定位函数(fseek, fsetpos, rewind), 在你向文件写入一些数据以后, 若是你又想从该文件读取一些数据, 你首先必须调用fflush或者文件定位函数
54.perror的使用方法
#include <stdio.h> int main(int argc, char **argv) { FILE *input = fopen("./a.txt", "r"); if (input == NULL) { perror("./b.txt"); exit(EXIT_FAILURE); } return 0; }
55.fclose(FILE *f)在关闭以前刷新缓冲区, 若是执行成功返回0, 不然返回EOF
56.输出缓冲区数据显示在屏幕上的条件
1.遇到\n
2.函数结束了
3.输出缓冲区满了
4.fflush(stdout)
测试:
printf("你好");
for(;;);
以上程序不会打印
57.编译器每次只能处理一个文件, 因此就能够理解为何只需声明而不用见到定义!
58.链接器把编译器生成的目标文件当作一组外部对象组成的, 每一个外部对象表明着机器内存的某个部分, 并经过一个外部名称来识别, 所以程序中的每一个函数和每一个外部变量若没有被声明为static, 就都是一个外部对象!
59.链接器载入目标模块和库文件, 处理外部对象命名冲突, 生成载入模块(可执行文件)
60.
int a;若是出如今全部函数体以外, 则被称为外部对象a的定义!这说明a是一个外部变量, 同时为a分配空间
extern int a;这个语句仍然说明a是一个外部对象, 可是因为有extern关键字, 就显式的说明了a的存储空间是在程序的其余地方分配的, 从链接器的角度来看就是一个队外部变量a的引用, 而不是对a的定义
//下面的函数在外部变量random_seed中保存了整形参数n的一份拷贝
int random_seed;
void srand(int n) {
extern int random_seed;
random_seed = n;
}
每一个外部对象都必须在程序某个地方定义!因此, 一个程序中若包括了语句 extern int a; 那么这个程序就必须在别的地方包括语句 int a; 这两个语句能够在同一个源文件中也能够位于不一样源文件中!若是同一个外部变量的定义不止一次, 大部分系统会拒绝接受该程序!两个具备相同名称的外部对象实际上表明的是同一对象!
61.若是是用#include一个头文件, 在编译阶段则至关于一个文件, 因此不能出现定义重复, 即便extern、staic, 能够在链接的时候使用, 好比如下状况
a.c
#include <stdio.h> extern int a; //说明a是定义在其余程序中的 int main(void) { printf("%d\n", a); return 0; }
b.c
int a = 10; //此处不能加extern 由于这才是最初始定义
执行 gcc a.c b.c 成功!
62.时刻记住include是在编译阶段, extern等链接属性是在链接阶段使用的, 仔细考虑下变量的链接属性
63