【C笔记】C语言知识点

  • 链接
    • C语言代码经过编译以后,并没有生成最终的可执行文件(.exe 文件),而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的。对于 Visual C++,目标文件的后缀是.obj;对于 GCC,目标文件的后缀是.o。
    • 编译只是将我们自己写的代码变成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的
    • 链接命令
      • gcc main.o sum.o -o sum.exe
        • -o表示指定最后可执行文件名
    • dos命令
      • 编译 gcc -c 文件名.c -o 文件名.o
      • 链接 gcc 文件名.o 文件名.o -o 文件名.exe
      • gcc 为编译器
    • 预处理指令#
      • 包含头文件
        • 采用头文件主要是为了使某些定义可以供多个不同的c源程序使用
        • 不必在此文件中将这些定义重复一遍,预处理程序将把头文件中的定义,通通都加到它所产生的输出文件中,以供编译程序对之进行处理
        • #include
          • 在指令处展开被包含的文件,包含可以是多重的,一个被包含的文件中可以包含其他文件
          • #include<文件名>,使头文件替换预处理指令行
          • <头文件名>,此为标准方式,编译系统从存放c编译系统的子目录中去寻找所要包含的文件
          • 预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件
          • "头文件名",先从用户的当前文件中寻找要包含的文件,若找不到,再按标准方式查找
          • stdio.h,包含了与标准I/O库有关的变量定义和宏经一以及对函数的声明
          • stdbool.h将bool定义为_Bool的同义词,同事定义了两个符号常量true和false,t为1,f为0
      • 宏定义及宏展开
        • 宏定义与宏展开预处理仅是简单的字符串替换,并不进行类型检查和语法出错检测
        • #define指令
          • 用来定义宏
          • #define宏标识符 宏标识符代表的特定字符串
          • 宏定义可以嵌套(一个宏定义替换其他宏定义组成公式)
          • 宏可以代表一个字符串常量,(需用"括起宏标识代表的特定字符串)
          • 带参数的宏
            • #define宏名(实参) 形参表达式
            • 宏展开后形参实参都应该包括在括号中
          • # 字符串化运算符
            • 将# 后跟着的参数转化为字符串,常跟带参数的宏定义连用
          • ## 把参数连接到一起,预处理时把符号两侧参数合并成一个符号
        • # undef取消宏定义
          • 宏定义的作用范围为定义开始到程序结束,可以用#undef取消宏定义,从此以后宏标识符出现不再替换
      • 条件编译
        • # if,检测后边跟的表达式是否为真,真就编译后边代码,直到出现#endif,#else或#elif为止,否则不编译
        • # endif,用于终止#if
        • # else,用在某个#if之后,当if不为真编译后边代码
        • # elif,综合else和if指令,用于嵌套条件判断
        • # ifdef,用于检测后边跟的宏是否定义,定义则编译后边代码
        • # ifdef和#ifndef,防止包含头文件重复
          • 如果包含该头文件,则不重复包含该头文件,若不包含,则包含该头文件
          • #ifdef等价于#if define,#ifndef等价于#if !define
      • 特殊符号处理
        • 识别特殊字符,用特定的值进行替换
        • _FILE_包含当前程序文件名的字符
        • _LINE_表示当前行数的整数
        • _DATE_包含当前日期的字符串(时间和日期都是从某一特定时间起点开始的长整数
        • _STDC_如果编译器遵循ansic标准,则此为非零值
        • _TIME_包含当前时间的字符串
        • _为双下划线
      • # error,是编译器显示一条错误信息然后停止编译
      • # line,改变_line_和_file_用法
        • 如# line 10,则此行初始化计数器,行号从下一行开始为10
      • # pragma,无正式定义,可自行定义,一般为禁止或允许警告信息
  • 编译
    • 编译器
      • 这就需要一个工具,将C语言代码转换成CPU能够识别的二进制指令,也就是将代码加工成 .exe 程序的格式;这个工具是一个特殊的软件,叫做编译器(Compiler)。
    • 编译器能够识别代码中的词汇、句子以及各种特定的格式,并将他们转换成计算机能够识别的二进制形式,这个过程称为编译(Compile)。
    • 编译命令
      • 编译main.c,用gcc -c main.c -o main.o
        • -c表示gcc只编译不连接,-o表示此次编译输出的二进制代码文件名为main.o
    • 链接
  • 数据类型
    • 整形符号
      • 有符号signed,最高位代表符号,0正1负,可以省略,如signed int等价 int
      • 无符号unsigned,空间均存放数值,%u输出
    • 标识符
      • 只能是字母数字和下划线,第一个字符不能是数字
    • 转义字符
    • 整形
      • 短整型 short int 2
      • 整型 int 4
      • 长整型 long int 8
    • 浮点型
      • 单精度 float 4
      • 双精度 double 8
      • 字符
        • char 1
  • 字符
    • 取余
      • %,两边必须为整形
  • 补码
    • 正数的补码是此数的二进制
    • 负数的补码,为此数的绝对值二进制,各位取反再加一
    • 最左边一位为符号,1为负,0为正
  • 数据转换
    • 浮点变整形,舍去小数之后的数值
    • 整形字符按ASCII互换
    • 长整变短整,低字节直接截断
  • 选择
    • if
      • if(表达式) 语句1 else 语句二
      • 表达式可以为关系表达式(两个数值进行比较,如a<b),逻辑表达式(与或非),数值表达式
      • 在没有复合语句{ }时,else总是和最近的if结合
    • switch
      • switch(表达式) {case 常量1: 语句1 case 常量2: 语句2 default: 语句3 }
      • switch后表达式,值为int类型,包括字符型
      • 可以没有default,此时若没有与case常量匹配的switch表达式则不执行任何语句,跳转执行下边语句
  • 循环
    • for
      • for(循环变量赋初值;循环条件;循环变量增值)语句
      • 表达式1,2,3可以省略,但1,2其后分号不能省略,for(;;)
      • 2省略后循环将无终止的执行
      • 1,3可以为逗号表达式,包含一个以上分简单表达式,中间以逗号隔开
      • 2可以为条件,逻辑表达式也可以为数值或字符表达式,只要其值非0就执行循环体
    • while
      • while(表达式) 语句
      • 若表达式为真,就执行循环体语句,为假就不执行
    • do while
      • do{语句}while(表达式);
      • 先执行循环体,然后再判断是否成立
  • 表达式
    • 关系表达式
      • 0代表假,1代表真
      • 两个数值进行比较,如a<b
      • 小于<,小于等于<=,等于==,不等于!=
    • 逻辑表达式
      • 0为假,非0为真
      • 逻辑与&&,逻辑II,逻辑非!
      • 与和或为双目运算符,要求有两个运算对象
      • 可以将关系运算和逻辑运算的结果存到一个逻辑型变量中,逻辑变量类型符为_Bool
    • 条件表达式
      • 表达式1?表达式2:表达式3
        • 判断表达式1,若为真执行2,否则执行3
    • 逗号表达式
      • 包含一个以上分简单表达式,中间以逗号隔开
      • 逗号表达式按自左向右顺序求解,整个表达式以最右边表达式的值为值
  • 返回
    • break 退出循环
    • continue 结束本次循环
    • return 结束函数并返回其后表达式
  • 输入
    输入输出由c标准函数库中的函数来实现,printf和scanf都是库函数的名字,在编译时的连接阶段与系统函数库相连后,在执行阶段,调用函数库中的printf函数​,头文件为,stdio.h
    • scanf(格式控制,地址表列)
      • 地址表列,由若干个地址组成的表列可以为变量地址,或字符串首地址,如&a
      • 格式声明,以%开始,以一个格式字符结束,中间可以插入附加字符
        • 如果在格式控制字符串中含有除格式声明以外的字符,要在输入时在相应的位置输入相同的字符
      • 输入字符时在两个数值之间需要插入空格或其他分隔符以区分数值
      • 输入数据是如有空格,回车,tab,或非法不属于数值的字符认为该数据结束
    • getchar
      • getchar()是在输入缓冲区顺序读入一个字符(包括空格、回车和Tab)
      • 当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符,getchar函数的返回值是用户输入的第一个字符的ASCII码。
      • 如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。
      • 后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
    • getch
      • 在C语言中,getch()是一个非标准函数,如果使用的话,要把头文件stdlib.h包含进来,不是像getchar()一样,是stdio.h中的函数。两者的区别主要是getch()是不需要按下回车键才从键盘缓存中读取数据,而getchar()是在输入的字符存放在键盘缓存中,等到按下回车键后,才从缓存中读取数据,直到最后一个回车键。
      • getch()直接接收控制台输入的字符,不论这个字符是什么。比如退格键backspace,getch()可以直接接受这个数据,而getchar()则无法获得这个数据。
      • getch()接收到的数据不会回显到显示器上,与之相对应的是getche(),会把字符回显到显示器上。getch()在C语言编程中,经常用来在程序执行后,起到暂时中止的作用,有点类似systme("pause")功能,这样在程序执行完后,加上getch()就可以将屏幕暂停。等按下任意键后再继续执行下面的语句。
    • gets
      • 在前面从键盘输入字符串是使用 scanf 和 %s。其实还有更简单的方法,即使用 gets() 函数。
      • char *gets(char *str);这个函数很简单,只有一个参数。参数类型为 char* 型,即 str 可以是一个字符指针变量名,也可以是一个字符数组名。
      • gets() 函数的功能是从输入缓冲区中读取一个字符串存储到字符指针变量 str 所指向的内存空间。
      • 当我们输入回车键的时候其实是输入了换行回车两个字符进去的,记住是先输入回车字符后输入换行字符,但是gets函数并不接受回车字符,把它当作结束符,即遇到回车就读取之前的数据,那么后面的换行符并没有读取,这就导致后面使用的gets函数会读取到换行符,读取到了换行符就不会读取后面的字符,且把它转换为终止符
    • fgets
      • 虽然用 gets() 时有空格也可以直接输入,但是 gets() 有一个非常大的缺陷,即它不检查预留存储区是否能够容纳实际输入的数据,换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。
      • char *fgets(char *s, int size, FILE *stream);
      • s 代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。size 代表的是读取字符串的长度。stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取,标准输入流就是前面讲的输入缓冲区。所以如果是从键盘读取数据的话就是从输入缓冲区中读取数据,即从标准输入流 stdin 中读取数据,所以第三个参数为 stdin。
      • 如果输入的字符串长度没有超过 size,那么系统会将最后输入的换行符 '\n' 保存进来,保存的位置是紧跟输入的字符,然后剩余的空间都用 '\0' 填充
    • 由于scanf空格为分隔符不能一次性输入多个字符串
      • c语言中 scanf后面一定要跟个 getchar
        • scanf()在读取输入时会在缓冲区中留下一个字符’\n’(输入完s[i]的值后按回车键所致),所以如果不在此加一个getchar()把这个回车符取走的话,gets()就不会等待从键盘键入字符,而是会直接取走这个“无用的”回车符,从而导致读取有误
        • c语言中在读取键盘数据时,一般是带缓存的数据输入,需要按回车键才能完成该“行”数据的输入确认。
        • 而 scanf()函数对这个回车确认符并不进行处理,回车符会留在输入缓存区中。
        • 因此,在下一个读“字符”操作函数(getchar, scanf("%c"), gets()等)运行时,会读到这个字符。
        • 而在读数值型数据或字符串时,scanf()会从第一个非空白字符(空白字符指:回车,空格,TAB键)开始读取,自动忽略前面的空白字符,而遇到空白字符结束该类型数据的输入。
    • int sscanf(     const char *buffer,     const char *format, [ argument ] ...   );
      • buffer 存储的数据 format 窗体控件字符串。 有关详细信息,请参阅"格式规范"。argument 可选自变量 locale 要使用的区域设置
      • 函数根据参数format(格式化字符串)来转换参数buffer指向的字符串,转换后的结果存于对应的可变参数内。
      • %*[width] [{h | l | L}]type 表示满足该条件的字符被过滤掉,不会向目标参数中赋值。②width表示最大读取宽度。当读入字符数超过该值,或遇到不匹配的字符时,停止读取。多数转换丢弃起始的空白字符。这些被丢弃的字符及转换结果添加的空结束符('\0')均不计入最大读取宽度。③ {h | l | L}为类型修饰符。h指示输入的数字数值以short int或unsigned short int类型存储;hh指示输入以signed char或unsigned char类型存储。l(小写L)指示输入以long int、unsigned long int或double类型存储,若与%c或%s结合则指示输入以宽字符或宽字符串存储;ll等同L。L指示输入以long long类型存储。④ type 为类型转换符,如%s、%d。
      • 1) []:字符集合。[]表示指定的字符集合匹配非空的字符序列;^则表示过滤。该操作不会跳过空白字符(空格、制表或换行符),因此可用于目标字符串不以空白字符分隔时。[]内可有一到多个非^字符(含连字符'-'),且无顺序要求。%[a-z]表示匹配a到z之间的任意字符,%[aB-]匹配a、B、-中的任一字符;%[^a]则匹配非a的任意字符,即获取第一个a之前的(不为a的)所有字符。^可作用于多个条件,如^a-z=表示^a-z且^=(既非小写字母亦非等号)。空字符集%[]和%[^]会导致不可预知的结果。
      • ①使用[]时接收输入的参数必须是有足够存储空间的char、signed char或unsigned char数组。[]也是转换符,故%[]后无s。 %[^]的含义和用法与正则表达式相同,故sscanf函数某种程度上提供了简单的正则表达式功能。② n:至此已读入值(未必赋值)的等价字符数,该数目必须以int类型存储。如"10,22"经过"%d%*[^0-9]%n"格式转换后,%n对应的参数值为3(虽然','未参与赋值)。'n'并非转换符,尽管它可用'*'抑制。C标准声称,执行%n指令并不增加函数返回的赋值次数;但其勘误表中的描述与之矛盾。建议不要假设%n对返回值的影响。
      • sscanf与scanf类似,都是用于输入的,只是后者以键盘(stdin)为输入源,前者以固定字符串为输入源。
      • 返回值
        • 函数将返回成功赋值的字段个数;返回值不包括已读取但未赋值的字段个数。 返回值为 0 表示没有将任何字段赋值。 如果在第一次读取之前到达字符串结尾,则返回EOF。
    • scanf_s
      • 很多带“_s”后缀的函数是为了让原版函数更安全,传入一个和参数有关的大小值,避免引用到不存在的元素
      • 需要在字符串之后指定字符串的空间大小,否则会出现未知错误。
  • 输出
    putchar输入字符,printf格式输出,puts输出字符串
    • printf(格式控制,输出列表)
      • 格式控制
        • 用双撇号括起来的一个字符串,称为转换控制字符串
          • 格式声明,由%和格式字符组成,如%d,作用是将输出的数据转换为指定的格式,然后输出,总是从%开始的
            • %f格式符,%m.nf指定数据宽度和小数位数,m为数据包括小数所占位数,n为小数位数
            • 小数截掉后会四舍五入
          • 普通字符,输出时原样输出的字符,逗号,空格和换行符也可以包括其他字符
        • %md %-md,按指定的宽度,m以十进制整形输出数据,若输出数据宽度大,原则按照实际位数输出,若输出数据宽度小于m那么左(-为右)补齐空格
      • 输出列表
        • 需要程序输出的一些数据可以是变量常量或表达式
    • sprintf
      • sprintf 返回以format为格式argument为内容组成的结果被写入string的字节数,结束字符‘\0’不计入内。
      • sprintf指的是字符串格式化命令,函数声明为 int sprintf(char *string, char *format [,argument,...]);
      • 主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串
      • ①string-- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
      • ②format-- 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是%[flags][width][.precision][length]specifier
      • ③argument]...:根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。
      • 返回值
        • 如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
  • 变量
    • 局部变量在栈中,从高地址分配到低地址。全局和静态变量在静态区,从低地址分配到高地址
    • 全局变量
      • 在所有函数外部定义,可以作用于所有函数(第一个字母大写)
      • 作用域
        • 从变量的定义初开始到本程序文件的结尾
    • 局部变量
      • 在一个函数内部定义,作用于单个函数
      • 在复合语句(花括号)定义的变量,只能在该符合语句内使用
  • 常量
    • const 类型 名
    • #define 宏名 宏内容
      • 编译时以 宏内容 来替换所有宏名
  • 函数
    • 函数就是功能,一个函数实现一个功能
    • 在main()函数中做声明是把有关函数的信息通知编译系统
    • 无参函数
      • 类型名 函数名(void){函数体}
    • 有参函数
      • 类型名 函数名(形参表列){函数体}
    • 调用函数
      • 函数名(实参表列)
      • 在调用时,应在调用前,写下声明函数的函数原型(函数首部)
      • 递归调用
        • 在调用函数的过程中直接或间接又调用该函数本身(应和选择连用,避免无限循环)
    • 返回值
      • 通过return语句将函数值带回主调函数,若不需要函数值,可以将函数类型定义为void类型
      • 在定义函数时要指定函数返回值类型,return语句表达式类型应和定义函数值类型相同
    • 形参和实参间的数据传递(单项传递)
      • 系统把实参的值传递个形参,该值在函数调用期间有效可以参加函数中的运算
      • 未出现函数调用时,形参并不占存储空间,而是调用时分配临时内存单元
    • 数组作为函数参数
      • 数组元素可以用作函数实参
        • 不能用作函数形参(函数在调用是为形参分配空间,但数组时整个空间一起分配,无法为单个元素分配空间)
      • 数组名做函数参数
        • 用数组元素做实参时,向形参传递的是元素值,用数组名做实参时,向形参(形参为数组名或指针变量)传递地址
        • 用数组名作函数实参不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共同占用同一段内存单元
        • 改编形参数组元素,实参数组元素也会发生变化
  • 储存
    • 1、栈区(stack)—由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
      • 申请
        • 只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
        • 在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
      • 内容
        • 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
        • 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
    • 2、堆区(heap)—一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表
      • 申请
        • 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
        • 堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
      • 内容
        • 一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
    • 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。-程序结束后有系统释放
    • 4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
    • 5、程序代码区—存放函数体的二进制代码。
    • 每一个变量和函数都有两个属性,数据类型和数据的存储类别
    • 存储类别
      • auto自动的
        • 动态的分配存储空间数据存储在动态存储区中
        • 函数中的形参和在函数中定义的局部变量包括在复合语句中定义的局部变量
        • 调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间,auto可以省略,大部分变量都是自动类型
        • 不在编译时赋初值,而是在函数调用时进行赋初值每一次函数调用重新给一次初值,相当于执行一次赋值语句
        • 若定义变量时不赋初值,他的值是一个不可预测的数值
      • static静态的
        • 使函数值调用后不消失继续保持原值,其占用的储存单元不释放,下一次调用还是原有值
        • 静态局部变量属于静态储存类别,在静态储存区内分配储存单元,在程序运行过程中不释放
        • 在编译时赋初值,运行期间只赋初值一次,此时其已初值(如果定义不赋初值,则编译时自动赋值0或空)
        • 静态局部变量在函数调用结束后依然存在,但是其他函数是不能引用他的
      • register寄存器的
        • 将局部变量的值放在CPU寄存器中(该变量使用频繁)
      • extern外部的
    • 动态储存
      • 在程序运行期间根据需要进行动态的分配
      • 函数形参,自动变量(没有用static声明的变量),函数调用时的现场保护和返回地址
      • 对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间,这种分配和释放,是动态的,如果在一个程序中两次调用同一个函数,则分配的局部变量的储存空间可能是不相同的
    • 静态储存
      • 在程序运行期间由系统分配固定的存储空间
      • 全局变量全部存放在静态存储区中,在程序开始执行时,给全局变量分配存储区程序执行完毕就释放
    • static
      • 对局部变量用static声明把它分配在静态储存区,该变量在整个程序执行期间不释放,所分配的空间始终存在
      • 对全局变量用static声明则该变量的作用域只限于本文件模块
  • 扩展变量作用域
    • 在同文件内引用外部变量的作用域
      • 如果外部变量,不在文件的开头定义,其起作用的范围之限于定义处到文件结束
      • extern
        • 在引用前对该变量做外部变量声明,表示把该外部变量的作用域扩展到此位置(extern int a),类型名可以省略(extern a)
    • 将外部变量作用域扩展到其他文件
      • 两个文件都需要用同一个外部变量,但不能分别定义(报错重复定义)
      • 在一个文件中定义外部变量,而在另一个文件中用extern做外部变量声明(这样编译和链接时系统会有知道这个外部变量有一个外部链接,可以从别处找到已定义的此外部变量)
    • 将外部变量作用域限制到本文件
      • 在定义外部变量时加一个static(静态外部变量)
  • 变量的定义和声明
    • 定义性声名(定义)
      • 需要建立储存空间如int a
    • 引用性声明(声明)
      • 不需要建立储存空间extern a
  • 内部函数和外部函数
    • 函数的本质是全局的,不加声明的话一个文件定义的函数可以被其他文件的函数调用
    • 内部函数
      • 只能被本文件函数调用的函数
      • 函数类型和函数名前面加static(静态函数)
    • 外部函数
      • 可以被其他文件函数调用
      • 函数首部前加extern(定义时省略默认为外部函数)
  • 数组
    • 一维数组
      • 定义
        • 类型符 数组名[常量表达式]
        • 常量表达式用来表示数组个数,即数组长度,从0开始
        • 常量表达式可以包含常量和常量表达式如[3+5],但不能包含变量
      • 初始化
        • 在定义时对全部数组初始化
          • int a[10]={0,1,2,3,4}
          • 花括号内为初始化列表
          • 花括号内有五个值,数组长度为10,系统自动为后五个赋初值0
        • 对全部数组元素赋初值时,由于数据个数已经确定,可以不写数组长度int a[ ]={1,2,3}等同于int a[3]={1,2,3}
    • 二维数组
      • 1.数组名单独放在括号里面表示的是数组的地址
        • sizeof(a)(a单独放在括号内部,表示整个数组的地址) 求出来所占内存大小为12sizeof (a + 1)(表示第二个元素的地址(即2的地址)) 求出来大小为4这就是数组的地址
      • 2.对数组名进行取地址取出来的是数组的地址
      • 类型说明符 数组名[常量表达式] [常量表达式]
      • 二维数组中元素排列是按行排列的存放的,内存中各元素是连续存放的,不是二维的是线性的
      • 赋初值
        • 分行给二维数组赋初值int a[2][2]={{1,2}{3,4}}
        • 所以数据写一个花括号内int a[2][2]={1,2,3,4}
        • 对部分元素赋初值int a[2][2]={{1}{3}},此时各一维数组除第一个元素其余为0
        • 对全部元素赋初值(提供全部初始数据),则第一维可以不指定int a[2][2]={1,2,3,4}可以写成int a[ ][2]={1,2,3,4}
        • 可以不写第一维 然后分行赋初值 int a[ ][2]={{1}{3}},此时各一维数组除第一个元素其余为0
    • 字符数组
      • 字符数组一个元素存放一个字符
      • 字符串
        • 可以用字符数组存放字符串,字符串中的字符逐个存放到数组元素中
        • 字符串结束符
          • ′\0′,以′\0字符′作为结束标志,再遇到′\0′之前把前边的字符组成一个字符串
          • ′\0′代表ASCII码为0
          • 输出语句,输出字符串是系统会自动加上′\0′
        • 用字符串常量使字符数组初始化
          • char c[ ]={"字符串"}也可以省略花括号
          • 此时字符数组长度应为字符串长度+1,因为系统自动加上′\0′
      • 字符数组输入输出
        • %c输入或输出一个字符,%s对字符串的输入输出
        • 输出字符不包括′\0′,输出时遇到′\0′停止输出
        • %s输出项是字符数组名
        • 可以用scanf函数输入一个字符串,scanf("%s",c),c为已经定义字符数组名,输入字符串应短语数组长度
        • 可以同时输入多个字符串,以空格隔开
        • 系统把空格字符作为输入字符串之间的分隔符,即一个字符串内不能有空格
        • string.h
          • puts(字符数组)函数string.h
            • 将一个字符串输出到终端
            • 用puts()函数输出的字符串可以带有转义字符
          • gets(字符数组)函数string.h
            • 从终端输入一个字符串,并得到一个函数值,函数值为字符数组的起始地址
          • strcat(字符数组1,字符数组2)函数
            • 把数组2链接到数组1的后边,结果放着数组1中,结果得到数组1的地址
            • 数组1必须足够大
          • strcpy(字符数组1,字符数组2)函数
            • 字符串复制函数
            • 将字符数组2复制到字符数组1中
            • 字符数组1必须足够大,实参字符数组1必须写成字符数组名形式,字符数组2可以为变量名也可以是字符串
            • 不能将一个字符串或字符数组直接赋给字符数组
          • strncpy(字符串1,字符串2,n)
            • 讲2中前n个字符复制到1中
          • strcmp(字符串1,字符串2)
            • 比较字符串1和字符串2
            • 两个字符自左向右逐个比较(按ASCII码比较)知道出现′\0′为止
            • 如果全部字符相同,则字符串相同
            • 如果出现不同字符则按第一个不同字符为准(按英文字典在后边的大,小写大于大写)
            • 相等函数值为0,1>2函数值为一个正整数,1<2函数值为一个负整数
          • strlen(字符数组)函数
            • 测试字符串实际长度(不包括′\0′)
          • strwr(字符串)
            • 将字符串大写字母换为小写
          • strupr(字符串)
            • 将字符串小写换为大写
    • 可变数组(非静态数组)
      • 定义数组时数组长度为常量,但是如果在被调用的函数中定义数组,其长度可以是变量或非常量表达式
      • 调用函数是传递形参为数组长度,因为形参在执行函数时值是不变的
      • 但是如果指定为静态储存方式就不能使用
  • 指针
    • 地址指向该变量的储存单元,地址就是指针指向就是通过地址来体现的,一个变量的地址称为该变量的指针
    • 指针有两个属性:指向变量/对象的地址和长度,但是指针只存储地址,长度则取决于指针的类型,编译器根据指针的类型从指针指向的地址向后寻址
    • 指针类型不同则寻址范围也不同
    • 访问方式
      • 直接访问
        • 直接按变量名进行访问
      • 间接访问
        • 将变量存在另一个变量之中,通过另一个变量找到该变量地址,从而访问该变量
    • 指针变量
      • *指针运算符(间接访问运算符)
        • 星号如果不是在指针变量定义时出现,而是在某条语句的某个指针变量前出现,那么这个星号就是间接运算符,表示取指针所指向变量的值
      • 专门存放另一个变量的地址(指针),指针变量的值就是地址(指针)
      • 指针变量空间的大小
        • 定义指针变量后,系统会为该指针变量分配存放一个地址值的存储单元,存储单元的大小与该指针所指向内存空间的类型无关,32位系统分配四个字节,64位系统分配八个字节,sizeof运算符可以计算出指针变量所占空间大小
      • 定义指针变量
        • 类型名 * 指针变量名
        • 可以在定义时对他初始化
        • 在定义时必须指定基本类型,不同类型数据在内存所占字节数不同
      • * 变量名表示该指针变量指向的对像
      • 空指针
        • 空指针是其值为Null的指针(空指针是其值为Null的整数常量表达式,子头文件中宏Null被定义为空指针常量)
        • 空指针不指向任何地址,所以不能用间接运算符*来取值
        • 定义变量未初始化或者指向的目标已经销毁的指针称为悬浮指针
      • 万能指针(void指针)
        • 指向void的指针,其类型为void*,表示未确定类型的指针,不能用这种类型的指针直接获取所指向内存的内容,需要先转成合适的类型
        • 任何指针都可以赋值给void指针,void *vp  type *p; vp=p;//不需转换//只获得变量/对象地址而不获得大小
        • void指针赋值给其他类型的指针时都要进行转换 type *p=(type*)vp; //转换类型也就是获得指向变量/对象大小
        • 不能对void指针进行算法操作
          • void指针不能复引用 *vp//错误 ,因为void指针只知道,指向变量/对象的起始地址,而不知道指向变量/对象的大小(占几个字节)所以无法正确引用
          • 进行算法操作的指针必须是确定知道其指向数据类型大小的。
      • const指针常量
        • 常量指针
          • const 类型 * 指针变量名
          • 不能通过该变量,修改指向内容
            • 用const 类型*定义的指针变量是常量指针,不能通过指针变量的值来修改所指向的变量值,但被指向的变量可以通过自己来改变值,常量指针是限制这个指针修改所指向变量值
        • 常量指针变量
          • 类型 * const 变量名
          • 指针变量被定义为常量,定义时必须初始化,且不能再改变其值
        • 指针常量
          • const 类型 * const 变量名
          • 即是常量指针又是常量指针变量
            • 指针变量的值不能改变,同时也不能通过指针变量修改它所指向的变量的值
      • 引用指针
        • 给指针赋值
          • 指针变量名 =&变量名
        • 引用指针指向的变量
          • *指针变量名代表,指针变量指向的变量
        • 引用指针变量的值
          • 直接引用指针变量名,此时指针变量的值为它指向的变量的地址
    • 指针变量作为函数参数
      • 应用指针变量作为函数参数,在函数执行过程中,使指针变量所指向的变量的值发生变化,函数调用结束后这些变量值的变化依然保留
    • 通过指针指向数组
      • 数组元素的指针
        • 数组元素的指针就是数组元素的地址
        • 当指针指向数组元素的时候,对指针变量进行加减运算,改变的是指针所指向数组元素下标的加减如(P+1就是指向该数组元素的下一个数组元素)
        • P+1实际上是*(P+1)一个数组所占字节数
        • 在编译时,对数组元素a[i]就是按*(a+i)处理,即按数组元素首元素地址加上相应位移量得到元素地址
        • 两个指向同一个数组的不同数组元素的指针变量p1-p2为两个地址之差除以单个数组元素所占字节(两数组元素间隔)
      • 通过指针引用数组元素
        • 下标法a[i]
        • 指针法*(a+i)或*(p+i)
      • 用数组名做函数参数
        • 实参数组名代表一个固定的地址或者说是指针常量
        • 形参数组名并不是一个固定的地址,而是安置指针变量处理
      • 通过指针引用多维数组
        • 多维数组元素的地址
          • 一个二维数组int a[][4],数组名a,代表二位数组首元素的地址,现在的首元素不是一个简单的整形元素,而是由四个整型元素所组成的一维数组,其中a[0],a[1]都是一维数组名,即一维数组首地址,所以a[0]就是a[0][0]的地址,&a[0][0]
        • 指向多维数组元素的指针变量
          • 指向数组元素的指针变量
            • 指针变量指向二维数组中首地址,第a[i][j]个元素为,(i-1)*二维数组一维数组元素数+(j-1)
          • 指向m个元素组成的一维数组的指针变量
            • int (*p)[4],表示p为一个指向包含四个整形元素的一维数组([ ]的级别比指针运算符高,p会先和[]结合,变为数组名,所以()不能省略)
    • 指向字符串的指针
      • 用字符数组存放一个字符串,通过数组名和下标引用字符串中的一个字符,通过%S输出字符串
      • 用字符指针变量指向一个字符串常量通过指针变量引用字符串常量
        • C语言对字符串是按字符数组来处理的,在内存中开辟了一个字符数组,用来存放该字符串常量,但是这个字符数中是没有名字的,只能通过指针变量来引用
        • 因为是使用字符指针变量存放,其只能指向一个字符类型数据,不能把多个字符类型数据赋给一个指针变量
        • 在输出项中给出字符数组指针变量系统会输出该字符指针变量指向的字符串的第一个字符,然后使该字符指针变量加一,输出下一个字符,直到遇到字符串结束标志
    • 指向函数的指针
      • 在程序中定义了一个函数,在编译时系统为函数代码分配一段储存空间,这段储存空间的起始地址(入口地址)称为这个函数的指针
      • 函数指针就是函数的指针,它是一个指针,指向一个函数。
      • c语言中, 函数名也称为函数的指针,所以c语言中函数名就是一个指针。
      • 定义
        • int (*p)(int ,int),表示指向函数的类型为int且有两个整形参数的函数
        • 类型名 (*指针变量名)(函数参数表列)
      • 用函数指针变量调用函数时,只需要将(*指针变量名)代替函数名即可
      • 用指向函数的指针做函数参数
        • 再调用函数时,实参为两个指向函数的指针变量,这样可以把其他函数起始地址带入函数,即在函数中调用函数
    • 返回指针值的函数
      • 定义
        • 类型名 *函数名(参数表列)
      • 返回值为指针类型
    • 指针数组
      • 一个数组其元素均为指针类型,就是指针数组
      • 定义
        • 类型名 * 数组名 [数组长度]
    • 多重指针
      • 类型名 ** 指针变量名
      • *,指针运算符的结合性是从右到左
    • 指针数组做main函数形参
      • int main(int argc,char * argv[ ])
      • argc(参数个数)和argv(参数向量)是main函数参数,为程序命令行参数
      • 命令名 参数1 参数2。。。。
      • 文件名为X,参数为me,命令行写成 X me,中间空格分开,命令名应包括盘符路径
      • 有命令行传递给程序的参数,实质是操作系统将命令行参数(用空格隔开的若干个字符串)组织成一个字符串数组,然后将它传递给min函数
  • 字符数组和字符指针变量比较
    • 字符数组有若干个元素组成,每个元素中放一个字符,字符指针变量中存放的是地址,却不是将字符串放到字符指针变量中
    • 可以对字符指针变量赋值,但不能对数组名赋值
    • 数组可以在定义时对各元素赋值,但不能用赋值语句对字符数组中全部元素整体赋值
    • 编译时对字符数组分配若干储存单元,以储存各元素的值,而对字符指针变量则只分配一个储存单元
    • 指针变量的值是可以改变的,而数组名代表一个固定的值(数组元素首地址),不能改变
    • 字符数组中各元素的值是可以被改变的,字符指针变量指向的字符串常量中的内容是不可以被取代的
    • 可以用指针变量或数组,指向一个格式字符串(可变格式输出函数)
  • void指针类型(只有地址没有类型)
    • 不指向任何类型数据
    • 在将它赋给另一个指针变量时要进行类型转换如 p1=(int *)p2
    • 把void类型指针赋给不同基本类型的指针变量时编译系统会自动进行转换
  • 动态内存分配
    • 全局变量是分配在内存中的静态储存区的,非静态的局部变量,包括形参是分配在内存中的动态存储区的,这个存储区成为栈
    • C语言还允许建立内存动态分配区域存放一些临时用的数据,这些数据不必在程序声明部分定义,也不必等到函数结束时才释放,而是需要时开辟,不需要时随时释放,这些数据放在一个特别的自由存储区堆
    • 定义
      • stdlib.h
      • malloc函数
        • void *malloc(unsigned int size)
        • 在内存分配一个长度为size(非负)的连续空间(该存储区的初始值不确定),此函数返回值是所分配的第一个字节的地址(此函数是一个指针型函数,返回的指针指向该分配域的开头),分配失败返回Null
      • calloc函数
        • void * calloc (unisgned n,unsigned size)
        • 分配n个长度为size的连续空间,函数返回所分配区域的起始位置指针,若分配失败返回Null
      • free函数
        • void free(void *P)
        • 释放指针变量P所指向的动态空间,P应该为最近应malloc开辟时得到的返回值
      • realloc函数
        • void * realloc(void *p,unsigned int size)
        • 改变并从新分配已经通过malloc,calloc函数开辟的动态空间大小,P指向的空间大小变为size,P的值不变
  • 结构体
    • C语言允许用户自己建立不同类型数据组成的组合型的数据结构
    • 定义
      • struct 结构体类型名{成员表列}结构体变量名 ;(需要带有分号)
      • 结构体声明并不引起系统为该结构类型分配空间,只有在定义了该结构体类型的变量时才会为该结构类型分配内存空间
    • 可以先声明结构体类型,在定义该类型变量
    • 还可以不定义结构体名 直接在后面定义变量名,但是该结构体只能使用一次
    • 一旦声明了结构类型就可以像使用其他所有类型一样使用这种结构类型,可以定义具有这种结构类型的变量,定义指向这种变量的指针以及定义具有这种结构类型元素的数组
    • 结构体变量初始化
      • 定义结构体变量时初始化
        • 初始化列表是用花括号括起来的用逗号隔开的一些常量,这些常量依次赋给结构体变量中的各成员
        • struct Students a={.name="zhangsan"},其他未被初始化成员系统自动赋值0
        • 相同类型结构之间可以,相互复制
    • 引用
      • 结构成员运算符 (点运算符) .
      • 结构体变量名.成员名
      • .在各种运算符中级别最高,可以看成一体
      • 只能对最低级的成员进行赋值或存取以及运算
        • 如果成员本身又属于一个结构体类型则要用若干个成员运算符一级一级的找到最低一级的成员
      • 对结构体变量的成员可以像对普通变量一样进行各种运算
        • 赋值,加减
      • 同型结构体变量可以相互赋值
      • 可以引用结构体变量成员的地址,也可以结构体变量的地址
      • 输入时必须分别输入他们的值
    • 结构体数组
      • 定义
        • struct 结构体名{成员表列}数组名[数组长度]
        • 先定义结构体,在用结构体名定义数组
      • 初始化
        • 在定义数组的后边加上={初值表列}
    • 结构体指针
      • 一个结构体变量的起始地址就是这个结构体变量的指针
      • 指向结构体对象的指针变量即可指向结构体变量也可指向结构体数组中的元素
      • 可以把(*p) .name用p->name代替
        • ->称为指向运算符,通过指针访问结构类型变量的成员
    • 指向结构体数组的指针
      • 指向结构体数组的首元素
      • p加一后,指向下一个元素
    • 用结构体变量和结构体变量的指针作函数参数
      • 用结构体变量的成员做参数(和普通变量一样)
      • 用结构体变量做参数
        • 将结构体变量所占的内存单元中的内容全部按顺序传递给形参,形参必须是同类型的结构体变量(值传递)
      • 用指向结构体变量的指针做实参,将结构体变量的地址传给形参
    • 结构变量的内存分配
      • 结构体变量的内存空间大小为所有成员空间大小之和但需要考虑内存对齐问题
      • 结构体类型变量的内存空间大小为结构体成员中所占内存空间字节数最大的值的整数倍
  • 共用体
    • 用同一段内存单元存放不同类型的变量
    • 使用覆盖技术后,一个数据覆盖了前面的数据,这种事几个不同的变量共享同一段内存的结构称为共用体类型的结构
    • 对于联合的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了
    • 定义
      • union 共用体名{成员表列}变量表列
    • 结构体变量所占内存长度是各成员占的内存长度之和,共用体变量所占的内存长度等于最长的成员的长度
    • 引用
      • 共用体变量名.共用体成员
    • 特点
      • 同一内存段可以用来存放几种不同类型的成员,但在每一瞬间只能存放其中一个成员,而是存放几个
      • 每一个瞬时存储单元只能有唯一的一个内容,也就是说,在共用体变量中,只能存放一个值
    • 初始化
      • 可以对共用体变量初始化,但初始化表中只能有一个常量
      • 共用体变量中作用的成员是最后一次被赋值的成员,再对共用体变量中的一个成员赋值后,原有变量存储单元的值就被取代
      • 同类型共用体可以相互赋值
  • 内存对齐
    • 产生原因
      • 现在计算机内存空间都是按照byte字节划分的,理论上讲对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址上访问,这就需要各种数据类型按照一定的规则在空间上排列,而不是一个接一个的排放,这就是内存对齐。
      • cpu对内存的读取不是连续的而是分块读取的,块的大小只能是2i个字节数(内存存储粒度)从cpu的读取性能和效率来考虑,若读取的数据未对齐,则需要两次总线周期来访问内存,因而效率会大打折扣
    • 对齐原则
      • 结构体或联合体按照编译器默认的对齐方式有以下三个对其原则:
      • 数据成员对齐原则:结构联合(struct或union)的数据成员,第一个数据成员存放在offset(偏移)为0的地方,以后每个数据成员存储的起始位置都要从该成员占用内存大小的整数倍开始
      • 结构体作为成员的原则:如果一个结构中有某些结构体成员,则结构的成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始)
      • 结构(或联合)的整体对齐原则:在数据成员各自对齐后,结构(或联合)本身也要进行对齐,即以结构体内部占用内存空间最大的数据类型进行对齐。(等同于sizeof该结构体的结果必须是其内部最大成员占用内存的整数倍)
  • 位字段
    • 结构或者联合的成员也可以是位字段
    • 位字段是一个具有特定数量的位组成的整数变量,如果连续声明多个小的位字段,编译器会将他们合并成一个机器字
    • 位字段允许我们使用名称来处理位
    • 位字段主要用于一些使用空间很宝贵的程序设计中(嵌入式编程)
    • 定义
      • 类型【成员名称】 : 宽度
      • 类型用于指定一个整数类型(_bool,int,signed int,unsigned int)
      • 成员名称可选,如果声明了一个无名位字段,就无法获取它,无名位字段通常用于填充,帮助后续位字段在机器字对齐到特定地址边界
      • 宽度指位字段中位的数量,宽度必须为常量表达式,其值非负,小于等于类型宽度
      • 无名位字段宽度可以为0,此情况下下一个定义的位字段将会从新的可寻址内存单元开始
    • 初始化
      • 可以使用初始化列表方式,也可以将位字段看成结构成员,使用(.)(->)赋值
    • 可以对其像int,unsigned int一样进行算术运算
    • 位字段通常不会占据可寻址内存单元,故无法对其进行位运算和采用取地址运算符&
  • 枚举
    • 把可能的值一一列举出来,变量的值只限于列举出来的值的范围内
    • 定义
      • enum 枚举名{枚举常量}枚举变量名
    • C语言对枚举类型的枚举元素按常量处理,不能对它们赋值
    • 每一个枚举元素都代表一个整数,编译时按顺序默认值为0,1。。。,也可以人为的指定枚举元素数值,在定义时显式的指定
    • 枚举元素可以用来判断比较
  • 用typedef声明新的类型名
    • 可以用typedef指定新的类型名来代替已有的类型名
    • typedef int integer表示指定用integer为类型名,作用和int一样
    • 代替结构体
      • typedef struct{成员表列}Date,声明一个新的类型名Date,代表上边一个结构体类型,然后可以用新的类型名Date定义变量
    • 代替数组
      • typedef int num[100],声明num为整形数组类型名,num a;定义a为整形数组名有一百个元素
    • 代替指针
      • typedef char * String声明String为字符指针类型,String P,S[10]定义P为字符指针变量,S为字符指针数组
    • 代替指向函数的指针
      • typedef int (*p)(参数表列)声明P为指向函数的指针返回值为整形,P P1,P2定义p1,p2为P类型指针变量
    • 按定义变量的方式,把变量名换上新类型名并在最前面加上typedef,就声明了新类型名代表了原来的类型
      • 先按照定义变量的方式,写出定义体(int a)
      • 将变量名换成新类型名(将a换为integer)
      • 在最前面加上typedef(typedef int integer)
      • 然后可以用新类型名去定义变量(integer a)
  • 文件
    • 用户角度
      • 普通文件
        • 普通文件是指保存在磁盘或其他外部介质上的数据,即可以是原文件,目标文件,可执行程序等,也可以是一组代输入处理的原始数据的数据文件,还可以使存放一组输出结果的文件
      • 设备文件
        • 只与主机相连的各种外部设备,如显示器,打印机,键盘等。在操作系统中,把外部设备看做是一个文件来进行管理,把它们的输入输出等同于对磁盘文件的读和写
          • 通常把显示器定义为标准输出文件,如在屏幕上显示有关信息,就是像标准输出文件输出printf(),putchar()
          • 键盘通常被指定为标准的输入文件,从键盘上输入就意味着从标准输入文件输入数据,scanf,getchar,gets
    • 文件编码方式
      • 文本文件(C语言默认文件类型)
        • 房子图片码的方式已经保存了,在存储时每个字符对应一个字节,用于存放对应的ascll码,所以文本文件也成为ascll文件或字符文件,原本文件占用的存储空间相对大每个字符以ascll码形式存储便于对字符逐个操作(C语言默认文件类型,当未指出文件类型时按文本文件处理)
      • 二进制文件
        • 将数据按其内部形式直接保存到文件中,试用于非字符,所占用的存储空间小,不需要进行转换,方便数据存储,但输出的数据为内存格式,不能直接识别
    • 流式文件
      • C语言编译器在处理文件时,并不区分类型,都看作是字符流,按字节进行处理。输入/输出字符流的开始和结束受程序控制而不受物理符号(如回车)的控制
    • 存储方式
      • 顺序读写
        • 从上而下,依次读取文件的内容,保存数据时,将数据依附在文件的末尾,这种存取方式常用于文本文件,而被存取的文件称为顺序文件,
      • 随机读写
        • 多以二进制文件为主,以一个完整的单位进行数据的读取和写数,通常以结构为单位
    • 程序文件
      • 源程序文件.c
      • 目标程序文件.obj
      • 可执行文件.exe
    • 数据文件
      • 供程序运行时读写的数据
      • 操作系统把各种设备都统一作为文件来处理
      • 文件指存储在外部介质上数据的集合
      • 字符按ASCII形式储存,数值即可以为二进制形式也可以为ASCII形式
    • 数据流
      • 把外部介质上的文件数据读取到当前程序,称为输入流
      • 在程序中,把数据写到文件里,为输出流
      • 输入输出是数据传送的过程,数据如水一样从一处流向另一处,因此常将输入输出形象的称为流
      • 流表示了信息从源到目的的流动
      • 程序中可以使用三种标准流文件
        • 标准输入流
          • 从终端的输入
        • 标准输出流
          • 向终端的输出
        • 标准出错输出流
          • 当程序出错时将出错信息发送到中端
    • 文件名
      • 文件是指存储在外部介质上的相关数据集合
      • 文件名就是数据集合的名称
      • 文件名由 文件名.扩展名 表示,C语言常用字符数组存放文件名
      • 一个文件要有唯一的文件标识(文件名)
        • 文件路径
        • 文件名主干
        • 文件后缀
        • D:\cc\temp(文件路径) filel .(文件主干) dat(文件后缀)
      • 文件后缀
        • docword生产的文件
        • txt文本文件
        • dat数据文件
        • cC语言源程序文件
        • cppC++源程序文件
        • forFORTRAN语言源程序文件
        • pasPascal语言源程序文件
        • obj目标文件
        • exe可执行文件
        • ppt电子幻灯片文件
        • bmp图形文件
    • 数据文件分类
      • ASCII文件
        • 文本文件,每个字节放一个字符的ASCII代码
      • 二进制文件
        • 数据在内存中是以二进制形式存储的,如果不加转换地输出到外存(除了内存和CPU寄存器以外的储存硬件),就是二进制文件
        • 可以认为其是存储在内存数据的映像,可称为映像文件
    • 文件缓冲区
      • C语言文件依据是否设置缓冲区分为两种
      • 不设置缓冲区的文件处理方式必须使用较低级的I/O函数直接对磁盘存取
      • C语言通常采用带有缓冲区的文件处理方式,当使用标准I/O函数(stdio.h)时系统自动设置缓冲区,并通过数据流来读写文件
      • ANSIC采用缓冲文件系统处理数据文件,系统自动的在内存区中为每个正在使用的文件开辟一个文件缓冲区(输入输出文件缓冲区),从磁盘向程序数据区输入输出数据时,要装满缓冲区后才一起送出去
      • 当进行文件读取时,不会直接对磁盘进行读取,而是先打开数据流将磁盘上的文件信息复制到缓冲区内,然后程序再从缓冲区中读取所需要的数据
      • 当写入文件时,并不会马上写入磁盘中,而是先写入缓冲区,只有在缓冲区已满,或关闭文件时,才会将数据写入磁盘
      • 对于每个正在使用的文件,系统会自动在内存中为其开辟一个文件缓冲区以便对文件进行操作
    • 文件类型指针
      • 每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(文件名,文件状态,当前位置),这些信息保存在一个结构体变量中,由系统声明为FILE,在头文件stdio.h中
      • 定义此结构体变量后,一般不用变量名引用变量,而使用一个指向此结构体变量的指针变量来引用这些结构体变量(FILE * fP)定义一个指向文件类型的指针变量fp,此指针变量指向文件信息区的开头
      • 可以使fp指向某一个文件的文件信息区,通过该文件信息区中的信息就能访问该文件
      • FILE * 指针变量名
      • FILE是定义在stdio.h中的结构体类型,改结构含义文件名,文件状态,文件当前位置等信息
      • 标准设备文件指针
        • stdin 指向标准输入文件 键盘
        • stdout 指向标准输出文件 屏幕
        • stderr 指向标准错误文件 屏幕
    • 文件位置指针
      • 用于指向文件当前位置,对文件进行定位和读写操作
      • 打开文件时文件位置指针总是指向文件开头(第一个数据之前),当文件指针指向文件结尾时,表示文件结束
      • 读文件时
        • 总是从文件位置指针的位置开始,去读其后的数据,然后位置指针移到下一个尚未读的数据之前,来指示下一次文件读的位置
      • 写文件时
        • 写操作时,总是从文件位置指针所指的位置去写,然后移到刚写入的数据之后,来只是下次文件操作的位置
    • 打开与关闭文件
      • 打开文件实际上是建立文件的各种有关信息,使文件指针指向该文件
      • 关闭文件是指断开文件指针与文件之间的关系,从此进制在对该文件进行操作
      • 对文件的操作都是由库函数完成的,在头文件stdio.h中
      • 使用文件步骤(通过stdio中标准I/O库函数实现)
        • 打开
          • 将指针指向文件,为其开辟文件缓冲区
        • 操作
          • 对文件进行读,写,追加和定位操作
        • 关闭
          • 断开文件指针和文件的关联,释放文件缓冲区
      • 打开
        • 用fopen函数打开数据文件,返回值为指向文件名的指针(文件信息区的起始地址)
        • fopen(文件名,使用文件方式),如fopen("al","r"),al表示打开名字为al的文件,r表示read即读入
        • 通常将fopen函数返回值赋给一个文件类型指针变量
        • 该文件必须存在,当不能实现打开任务,将带回出错信息,此时会带回空指针Null
      • 关闭
        • 文件使用完后需要将文件关闭,释放相应的文件缓冲区,实际上就是断开指针与文件之间的关系
        • 用fclose函数关闭数据文件,当成功关闭返回值为0,否则返回EOF(-1)
        • fclose(文件指针)
        • 前面曾打开文件用fopen函数将其返回值赋给的指针变量
        • 在完成一个文件后应该关闭它,以防止被误用
        • 关闭就是撤销文件信息区和文件缓冲区,使指针变量不再指向该文件
    • 使用文件方式
      • r的方式只能用于像计算机输入而不能用作向该文件输出数据
      • w方式打开的文件只能用于向该文件写数据(输出文件)而不能用于向计算机输入
      • a表示向文件末尾添加新数据
      • r+,w+,a+方式打开的文件即可以用来输入数据也可以用来输出数据
        • r+,rb+写入时新数据只覆盖所占的空间后边的数据不变,读写时可由位置函数(rewind)设置读写位置
      • r只读
        • 为输入数据打开一个已有文本文件,若文件不存在,出错
      • w只写
        • 为输出数据打开一个文本文件,若不存在建立新文件
      • a追加
        • 向文本文件结尾添加数据,若不存在出错
      • rb只读
        • 为输入数据,打开一个二进制文件,若不存在出错
      • wb只写
        • 为输出数据,打开一个二进制文件,若不存在建立新文件
      • ab追加
        • 向二进制文件添加数据,若不存在出错
      • r+读写
        • 为了读和写,打开一个文本文件
      • w+读写
        • 为了读和写新建一个文本文件
      • a+读写
        • 为了读和写打开一个文本文件
      • rb+读写
        • 为了读和写打开一个二进制文件
      • wb+读写
        • 为了读和写新建一个二进制文件
      • ab+读写
        • 为了读和写打开一个二进制文件
    • 顺序读写文件
      • 文件打开之后,就可以进行读写了
      • 顺序读写时,先写入的数据存放在文件中前边的位置,后写入的文件存放在文件后边的位置
      • 顺序读时,先读文件前边的数据,再读文件后边的数据
      • 在访问磁盘文件时,是逐个字符(字节)进行的,为了知道当前访问到第几个字节,系统用"文件读写位置标记"来表示当前访问位置,开始时标记指向第一个字节,每访问玩一个字节后,标记就指向下一个字节
      • feof函数确定是否到达文件末尾
        • feof(pt)检查pt指向的文件是否结束,是返回1,否则返回0
      • 读入或写入一个字符
        • fgetc函数
          • fgetc(pt)从pt指向的文件读入一个字符,读成功带回字符ASCII码,失败返回EOF(-1)(表示文件结尾或者文件打开方式不含r或+)
          • 变量可以是int或者char
        • fputc函数
          • fputc(ch,pt)把字符ch写到文件指针pt所指向的文件中,输出成功返回输出字符ASCII码,失败返回EOF
          • 写入的文件可以是w,w+,a但用w,w+方式打开一个已经存在的文件时将清空原有文件内容,写入字符从文件首开始
          • fputc和pgetc函数都被宏定义为putc和getc
    • 格式化读写文件
      • 出来文件指针参数外,与scanf和printf相同
      • fprintf(文件指针,格式字符串,输出表列)
        • 将变量表列数据,按照格式控制字符串写入到文件指针指向的文件
      • fscanf(文件指针,格式字符串,输入表列)
        • fscanf(fp,"%d",&a)
        • 成功返回读入参数个数,失败返回eof
    • 向文件读写一个字符串
      • fgets函数
        • fgets(str,n,fp)从pt指向的文件中读入一个长度为n-1的字符串,并在其后加上\0,然后把这n个字符放在字符数组str中,成功返回地址str,失败返回Null
        • 在读完n-1个字符之前,若遇到换行符\n或者文件结束符EOF读入结束,但所遇到的换行符\n也作为一个字符读入
        • 函数成功执行,返回的是str数组首元素的地址,如果一开始就遇到文件尾或读数据出错则返回Null
      • fputs函数
        • fputs(str,pt)把str所指向的字符串写入到文件指针变量pt所指向的文件中,成功返回写入文件字符个数,否则返回Null
        • str可以为字符串常量,字符数组名或者字符型指针
    • 用二进制方式向文件读写一组数据
      • 用fread和fwrite读写文件时,文件的打开方式应该是二进制文件,即使用文本文件,系统也会按二进制文件读写
      • fread(buffer,size,count,fp)
        • 从fp指定的文件中按照二进制形式将size*count个字节的数据读到buffer指定的数据区中
      • fwrite(buffer,size,count,fp)
        • 按二进制形式,将buffer指定数据缓冲区内的size*count个字节的数据(一块内存区域的数据)写入由fp指定的文件中去
      • buffer是一个地址,存放读入读出数据的起始地址
      • size要读写的字节数
      • count要读写多少个数据项(每项长度为size)
      • fp文件类型指针
      • 返回值
        • 成功返回实际读写数据块的数量,若出错返回0
    • 随机读写数据文件
      • 文件位置标记
        • 顺序读写每写完一个数据后,文件位置标记顺序后移一个位置,然后下次执行写操作时把数据写入指针所指位置
        • 根据需要人为的移动文件位置标记位置然后对改位置进行读写,这是随机读写
      • 文件位置标记的定位
        • 用rewind函数使文件位置标记指向文件开头
          • 使文件位置标记指向文件开头,此函数没有返回值
        • 用fseek函数改变文件位置标记
          • fseek(文件类型指针,位移量,起始点)
          • 起始点用0,1或2代替,0为文件开始位置,1为当前位置,2为文件末尾位置

          • 位移量指以起始点为基点,向前移动的字节数,应是long类型(在数字后加L)
          • 一般用于二进制文件
        • 用ftell函数测定文件位置标记的当前位置
          • 得到流式文件标记的当前位置,用相对于文件开头的位移量来表示,若出错则返回-1L
    • 文件检测函数
      • feof函数(文件结束检测函数)
        • feof(文件指针)
        • 如果文件结束返回1,否则返回0
        • 可以用符号常量EOF判断是否达文件尾
      • ferror函数(文件出错检测)
        • 在调用各种输入输出函数时如果出现错误除了函数返回值有所反馈外,还可以用ferror函数检测
        • ferror(fp),返回0表示未出错,非0表示出错
        • 对同一个文件调用输入输出函数都会产生一个新的ferror函数值
        • 在执行fopen函数时ferror函数初始值自动为0
      • clearerr函数(文件出错标志和文件结束标志置0函数)
        • 使文件错误标志和文件结束标志置0
        • 只要出现读写错误标准它就会一直保留,直到对同一个文件调用clearerr函数或rewind函数或任何其他一个输入输出函数
  • sizeof()函数
    • sizeof(表达式)
    • 测定表达式长度
发布了8 篇原创文章 · 获赞 0 · 访问量 110

猜你喜欢

转载自blog.csdn.net/qq_43915356/article/details/105291404