C语言入门

gcc的简单使用

gcc 源自GNU

  1. gcc -E hello.c -o hello.i
    • 预处理.c文件,处理文件包含,宏定义,条件编译
  2. gcc -S hello.i -o hello.s
    • 编译,将预处理过的文件编译成汇编文件。过程包括:词法分析,语法分析,语意分析,代码生成等。
  3. gcc -c hello.s - o hello.o
    • 汇编,生成目标文件,此时是二进制代码。目标文件没有链接动态库,还不是可执行文件。
    • 静态库生成(ar -rc libxxx.a xxx1.o xxx2.o xxx3.o),静态库以.a后缀结尾
    • 动态库的生成(gcc -fPIC -shared xxx1.c xxx2.c xxx3.c -o libxxx.so),动态库以.so后缀结尾
  4. 链接:目标文件必须链接过后才可以执行
  • 静态链接:gcc -o hello hello.o libxxx.a
  • 动态链接:gcc -o hello hello.o libxxx.so
  • lld.hello.exe:在linux中可以通过shell脚本 lld 查看可执行模块的dependency

基本语法

  1. system函数:int system(const char * string);
    • 在库<stdlib.h>中,作用是开辟一个新的进程执行(调用fork()),来执行参数字符串所代表的命令。
    #include <stdio.h>
    #include <stdlib.h>
    // gcc 中有这个库
    #include <windows.h> 
    
    int main() 
    {
      printf("before\n");
      // 系统调用
      system("dir");
      // 调用外部程序
      system("hello.exe");
      printf("after\n");
      // 使用 windows提供的库函数,调用windows操作系统的功能
      WinExec("calc", SW_NORMAL);
      return 0;
    }
    
    • 宏定义,会在预编译时展开
    // 定义一个宏 main.c
    #define NUM 100
    int main(int argc, char const *argv[])
    {
      int a;
      // 预处理会展开宏
      a = NUM; 
      return 0;
    }
    
    // 使用 `gcc -E`  预编译后 main.i
    # 1 "main.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "main.c"
    int main(int argc, char const *argv[])
    {
      int a;
      100 = 10;
      a = 100;
      return 0;
    }
    
  2. 进制表示
    • 0x:十六进制,0:八进制
    • 输出时,%d:有符号十进制,%u无符号十进制输出,%o:无符号八进制,%x:无符号十六进制,输出时,以格式化输出的类型来决定输出的类型,输出时都是以四字节输出。在以%u,%o,%x输出时,如果原数据最高位是1,则高位补1。
    char b = 0x81;
    printf("%x\n", b); // 输出ffffff81
    
    • 计算机在存储时,都是以二进制补码形式存储,且每种类型的第一位为补码,注意在以%d输出时的进制转换问题。
    char b = 0x81;
    printf("%x\n", b); //输出 -127
    
    // 1000 0001 char 类型是8位存储,第一位为符号位
    // 原码为 1111 1110 + 1 = 1111 1111 = -127
    
    • 如果以小容量类型存储大容量类型的数据,超出部分被舍弃。以大容量数存储小容量数据,如果最高位是1,则在前面全部补1补齐位数。
  3. 数据类型:每种数据类型都有一定的存储大小,如果超过了存储空间大小,多出的位数则会舍弃,所以在给一种类型赋值或计算时,应注意该数据类型的取值范围,如果越界,可能产生意料之外的结果。
    • 整型
      • char、unsigned char:占一个字节。'a'为字符常量,打印格式为%c"a"为字符串(字符数组),打印格式为%s,每个字符串结尾,编译器都会自动添加一个结束标志位 \0,两者不一样。-128 到 127 或 0 到 255。
      • short、unsigned short类型,存储整数,2字节
      • int、unsigned int类型,存储整数,4字节
      • long、unsigned long,4字节
    • 浮点型
      • float,4字节,精度是六位小数
      • double,8字节,精度15位小数
      • long double,16字节,精度19位小数
    • 数组
      • 声明
        int arr[2] = {1, 2}
      • 数组越界:编译时不会出错,但是当程序运行时,如果用到了存储越界数组项这块空间时,会报错。
      • 数组名指向数组的首地址。c语言里面没有获取数组长度的方法,sizeof(arr) / sizeof(arr[0])可以获取数组长度。
      • 多维数组:int arr[2][3] = { { 1, 2, 3 },{ 4, 5, 6 } }
      • 字符数组和字符串:字符串以\0 或 0结尾。字符串可以以%s格式输出,原因是字符串输出指针需要结束符0
      #include <stdio.h>
      int main(int argc, char const *argv[])
      {
        char ch[] = { 'h', 'e', 'l', 'l', 'o' }; // 字符数组,长度是5
        char str2 = "hello" // 这种方式初始化,默认加 \0 结束符,所以长度是6
        char str[] = { 'h', 'e', 'l', 'l', 'o' '\0'}; // 字符串
        char str1[] = { 'h', 'e', 'l', 'l', 'o' 0}; // 字符串
        return 0;
      }
      
  4. 流程控制s
    • for(continue,break,goto)
    #include <stdio.h>
    int main(int argc, char const *argv[])
    {
      goto next; // 跳转到同一个作用域的标记处
      printf("zzzz1\n");
      printf("zzzz2\n");
      next:
        printf("123\n"); // 执行这里
      return 0;
    }
    
    • while
    • do...while
  5. 库函数
    • 随机数
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main(int argc, char const *argv[])
    {
      /*
      1. srand函数是随机数发生器的初始化函数。
      2. 原型:void srand(unsigned int seed),如果传入参数相同,则产生随机数也相同。
      3. srand和rand()配合使用产生伪随机数序列。
      */
      srand((unsigned int) time(NULL));
    
      int i = 0;
      int num;
      for(i = 0; i < 5; i++) 
      {
        // rand()函数返回一个随机数
        num = rand();
        printf("%d\n", num);
      }
      return 0;
    }
    
    • 字符串处理函数
      • 每个字符串常量都是一个地址,指向字符串的首元素地址。字符串存储在_data的文字常量区,相同的字符串拥有相同地址。
      • gets(char []):获取一个键盘输入的字符串,以回车结束,和 scanf() 不同的是它可以读取空格,这个函数不安全,已经弃用。
      • fgets(char *, size, *stream);:可以指定读取长度,换行符也会读取。
      • strlen(s1):返回字符串s1的长度。和sizeof()不同的是,strlen()不包括结束符\0的长度,而且遇到\0结束。
      • strcpy(dst, src):字符串拷贝,以首元素开始,遇到\0结束。
      • strncpy(dst, src, length):字符串拷贝,可以拷贝\0,但是不能拷贝\0后面的数据。
      • strcmp(s1, s2):如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。依次比较每个字符的ASCII码。
      • strncmp(s1, s2, n):可以指定比较长度n
      • strcat(s1, s2),字符串拼接。
      • sprintf(buf, "%d %d %d", a, b, c),将数据(a,b,c)以指定格式填充到buf中。
      #include <stdio.h>
      #include <string.h>
      
      int main(int argc, char const *argv[])
      {         
        int a, b , c;
        char buf[100];
        a = 1, b = 2, c = 3;
        sprintf(buf, "%d %d %d", a, b, c);
        printf("buf = %s\n", buf); // buf = 1 2 3
        return 0;
      }
      
      • sscanf(buf, "%d %d %d", a, b, c):将buf中的数据以指定格式提取到(a,b,c)中。注意格式要严格一致。
      #include <stdio.h>
      #include <string.h>
      
      int main(int argc, char const *argv[])
      {         
        int a, b , c;
        char buf[] = "a = 1, b = 2, c = 3";
        sscanf(buf, "a = %d, b = %d, c = %d", &a, &b, &c);
        printf("a = %d b = %d c =  %d\n", a,b,c); // a = 1 b = 2 c =  3
        return 0;
      }
      
      • strchr(s1, ch):返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
      • strstr(s1, s2):返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
      • char *strtok(char *str, const char *delim):该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针,该函数会破坏原字符串,在匹配的位置加上\0。第二次调用时,第一个参数写NULL =>char *strtok(NULL, const char *delim),切割的还是第一次调用被破坏的字符串。
  6. 函数
    • 库函数,使用头文件引入,然后使用。
    • 自定义函数。
    #include <stdio.h>
    // 无参 无返回值
    void hello()
    {
      printf("hello func\n");
    }
    // 含参无返回值
    void add(int a, int b){
      printf("%d\n", a + b);
    }
    // 含参有返回值
    int multiply(int a, int b){
      return a * b;
    }
    int main(int argc, char const *argv[])
    {
      hello();
      add(1, 2);
      printf("2 * 3 = %d\n", multiply(2, 3));
      return 0;
    }
    
    • 函数的声明和定义: 在main函数中调用其他函数时,只会往前去找函数的定义,如果没有定义就找函数的声明, 如果声明也没有,c编译器会报警告,c++会报错。所以在调用函数之前,一定要进行函数的声明,声明时形参名可以不写。
    #include <stdio.h>
    // 这里是函数的声明
    int func(int a, int b);
    int main(int argc, char const *argv[])
    {
      func(1, 2);
      return 0;
    }
    // 这里是函数的定义
    int func(int a, int b) {
      return a + b;
    }
    
    • 封装库函数。在.c文件中编写自定义函数,在.h文件中写函数声明,在用到这些函数时,先将.h文件包含进来,(用双引号引入),将这些c文件一起打包编译。
    • .h文件为了防止被多次包含,可以在第一行加入。
    // 1. 条件编译
    #ifndef MY_STRLEN
    #define MY_STRLEN
    extern int my_strlen(char arr[]);
    #endif 
    // 2. 或者在第一行加入
    #pragma once
    

指针

  1. 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
    • 指针也是一种数据类型。
    • 指针指向谁,就把谁的地址赋值给指针
    • 直接操作指针变量本身没有意义,需要操作*p---指针所指向的内存。
    • 指针的大小是由编译器决定的。
    #include <stdio.h> 
    int main(int argc, char const *argv[])
    {
      int *p;
      int a = 10;
      p = &a;
      printf("%p %p\n", p, &a); // 0061FF28 0061FF28
      *p = 10;
      printf("%d %d\n", a, *p); // 10 10
      return 0;
    }
    
  2. 野指针:指针保存了一个非法地址。(只有定义变量后系统分配的地址才是合法的),操作野指针时不会操作,但是在操作野指针指向的内存时,由于系统在该进程中未对改地址授权,会报一个段错误。
  3. 空指针:给指针变量赋值为NULL
int *p = NULL;
// 相当于是
int *p;
p = NULL;
  1. 多级指针:指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
#include <stdio.h>
int main(int argc, char const *argv[])
{
  int a = 10;
  int *p = &a;
  int **q = &p;
  *p = 20; 
  **q = 30;
  // a= 30 *p = 30 **q = 30 
  printf("a= %d *p = %d **q = %d \n ", a, *p, **q);
  return 0;
}
  1. 指针操作方式
#include <stdio.h>
int main(int argc, char const *argv[])
{
  int *p;
  int a = 10;
  p = &a;
  // a = 10 *p = 10 *(p+0) = 10 p[0] = 10
  printf("a = %d *p = %d *(p+0) = %d p[0] = %d\n", a, *p, *(p+0), p[0]);
  return 0;
}
  1. void类型指针
#include <stdio.h>
int main(int argc, char const *argv[])
{
  int a = 10;
  /* 
    1. 不能用 void 定义变量,因为无法确定变量要分配的内存大小
    2. 但是可以用 void 关键字定义指针,因为指针的大小是固定的,根据编译器来确定。
    3. 在使用 void 关键字定义的指针是,需要进行类型适配。 
        * 理由是地址指向的都是这个数据的首地址(int 4 字节的首地址),操作整个4
          字节的数据,但是 void 类型没有大小无法确定操作多大空间。
  */ 
  void *p;
  p = &a;
  //printf("*p = %d\n", *p); // error: invalid use of void expression
  printf("*p = %d\n", *(int *)p); // 把 void 类型的指针转换成 int 类型的指针 
  return 0;
}
  1. 指针的步长,指针的加减法根据指针的步长来进行加减。指针的步长由指针的类型决定,比如int类型的指针步长是4。
#include <stdio.h>
int main(int argc, char const *argv[])
{
  int a = 10;
  int *p = &a;
  printf("p = %p P+1 = %p\n", p, p+1); // p = 0061FF28 P+1 = 0061FF2C
  return 0;
}
  1. 指针与数组
    • 数组存储的是指针类型的数据,每个数据是一个指针。
      int *p[3];
    • 形参中的数组:形参中的数组不是数组,是一个指针。
    func(int a[100]){} // 形参大小是一个指针的大小
    func(int a[]){} // 形参大小是一个指针的大小
    func(int *a){} // 形参大小是一个指针的大小
    
    • 数组名是一个指针,指向数组第一个元素的地址,可以利用指针步长的特性来遍历数组。
  2. 指针作为函数返回值:注意,不能返回一个局部变量的地址,局部变量在函数调用完成时会自动销毁。如果再用一个指针去接收这个地址会产生一个野指针。
#include <stdio.h>

int a; // 全局变量 在 bss区(未初始化全局变量)

int *func(){
  return &a;
}

int main(int argc, char const *argv[])
{
 *func() = 100;
  printf("a = %d\n", a);
  return 0;
}

作用域

  1. 局部作用域
    • {}内定义的变量只能在该局部作用域内使用,在代码执行到这个变量的定义时,编译器才会为这个变量分配空间。
    • 在不同的作用域中可以定义同名的变量。
    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
      int a = 1;
    
      if(1) {
        int a = 2;
        printf("%d\n", a); // 2
      }
      return 0;
    }
    
    • static关键字声明的静态局部变量在程序编译的时候就会分配地址空间(_data区),不能用局部变量初始化,静态局部变量在程序退出时销毁。static初始化的局部变量只会初始化一次,但是可以赋值多次。
    #include <stdio.h>
    void func(){
      static int a = 1;
      a++;
      printf("%d\n", a);
    }
    int main(int argc, char const *argv[])
    {
      func(); // 2
      func(); // 3
      func(); // 4
      return 0;
    }
    
  2. 全局作用域
    • 全局变量,在编译时分配地址空间,如果没有初始化,默认值为0,在程序(进程)结束时销毁,提前使用(定义前使用)需要先声明,可以声明多次。
    #include <stdio.h>
    void func(){
      extern int a; // 在这之前未定义,需要声明
      printf("func = %d\n", a);
    }
    
    int a = 10;
    
    void func1(){
      printf("func1 = %d\n", a);
    }
    
    void func2(){
      int a = 20;
      printf("func2 = %d\n", a);
    }
    
    int main(int argc, char const *argv[])
    {
      func();
      func1();
      func2();
      return 0;
    }
    
    • static全局变量(包括函数),和普通全局变量的区别就是作用域不同,只在本文件内可用。

内存分配

  1. 在程序执行前,有几个分区的内存已经确定,可以在linux使用size命令查看。

    • text(代码区):一般只读,函数。
    • data:已初始化的数据,全局变量,static变量,文字常量区(只读)。
    • bss:未初始化数据,全局变量,static变量
      目标文件段大小
  2. 在程序运行时除了加载上述已确定的内存外,还加载包括堆区内存和栈区内存。

    • 栈内存(stack):存放普通局部变量,自动管理内存。


      查看栈空间
    • 堆内存(heap):手动申请空间,手动释放或程序结束系统释放,使用函数malloc(int size)申请堆内存。
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
      int *p;
      p = (int *)malloc(sizeof(int));
      if(NULL == p){
        return -1; // 空间分配失败
      }
      *p = 100;
      printf("%d\n", *p); // 100
      printf("%p\n", p); // 00FF2A48
      if(NULL != p){
        // 释放 p 所指向的内存, p 中存储的地址信息不变,这个地址不能再被使用。
        free(p); 
        p = NULL;
      }
      //*p = 20; // err
      return 0;
    }
    }
    

结构体

  1. 定义和声明
#include <stdio.h>
#include <string.h>

struct Student
{
  char name[50];
  int age;
};

struct Teacher // 定义并声明
{
  char name[50]; 
  int age;
}t1, t2 = { "hgz",18 };

struct // 定义匿名结构体并声明
{
  char name[50];
  int age;
}w1 = { "hgz",18 }, w2;


int main(int argc, char const *argv[])
{
  struct Student stu1 = { "hgz",18 };

  struct Student stu2;
  strcpy(stu2.name, "hgz");
  stu2.age = 18;

  struct Student stu3;
  struct Student *p;
  p = &stu3;
  strcpy(p->name, "hgz");
  p->age = 18;

  printf("%s\n", "ok");
  return 0;
}
  1. 结构体数组
struct Student stu[2] = {
  {"hgz", 18},
  {"zzz", 19}
};
  1. 结构体直接作为参数传递是值传递,如果需要修改原结构体,需要用地址传递(参数为结构体指针)。
#include <stdio.h>

struct Student
{
  int age;
};

void func(struct Student *p){
  p->age = 20;
}

int main(int argc, char const *argv[])
{
  struct Student s1 = {18};
  func(&s1);
  printf("%d\n", s1.age);
  return 0;
}
  1. 结构体中的指针。(要先给地址再使用,可以给文字常量区字符串地址,栈地址或者堆区地址)
#include <stdio.h>
#include <string.h>

struct Student
{
  char *name;
  int age;
};


int main(int argc, char const *argv[])
{
  // 相当于 s1.name = "zzz"; 将data区文字常量的字符串的首地址赋值给指针
  struct Student s1 = {"zzz", 18}; 
  printf("s1 = %s %d\n", s1.name, s1.age);

  struct Student s2;
  // strcpy(s2.name, "hgz"); // err 野指针 s2.name 还没有地址
  char name[100];
  s2.name = name; // 分配栈区空间
  strcpy(s2.name, "hgz");
  s2.age = 20;
  printf("s2 = %s %d\n", s2.name, s2.age);

  struct Student s3;
  s3.name = (char *)malloc(strlen("hgzzz")); // 分配堆区空间
  strcpy(s3.name, "hgzzz");
  s3.age = 22;
  printf("s3 = %s %d\n", s3.name, s3.age);
  if(NULL != s3.name){ // 注意使用堆区地址时,使用完要释放
    free(s3.name);
    s3.name = NULL;
  }
  return 0;
}

共用体

  1. 共用体所有成员公用一块内存地址,内存大小为最大成员内存大小。所有成员都指向首地址。
#include <stdio.h>

union Test{
  unsigned int a;
  unsigned short b;
  unsigned char c;
};

int main(int argc, char const *argv[])
{
  union Test t1;

  // &t1.a = 0061FF2C &t1.b = 0061FF2C &t1.c = 0061FF2C 共用体成员指向同一块地址头
  printf("&t1.a = %p &t1.b = %p &t1.c = %p\n", &t1.a, &t1.b, &t1.c);

  t1.a = 0x44332211;
  // t1.a = 44332211 t1.b = 2211 t1.c = 11 共用体公用一块内存
  printf("t1.a = %x t1.b = %x t1.c = %x\n", t1.a, t1.b, t1.c);

  t1.c = 0xaa;
  // t1.a = 443322aa t1.b = 22aa t1.c = aa 修改共用体中的成员可能会影响其他成员
  printf("t1.a = %x t1.b = %x t1.c = %x\n", t1.a, t1.b, t1.c);

  return 0;
}

枚举类型

  1. 定义
#include <stdio.h>

enum{ // 不初始化赋值默认为 0 开始递增
  pink, red, yellow
};

int main(int argc, char const *argv[])
{
  printf("%d\n", pink == 0 ); // 1
  printf("%d\n", red == 1 ); // 1
  printf("%d\n", yellow == 2 ); // 1
  return 0;
}
  1. typedef关键字:给一个已存在的类型取一个别名。
#include <stdio.h>

int main(int argc, char const *argv[])
{
  typedef int int32; // 当前使用的是32位gcc编译器

  typedef struct Student
  {
    int32 age; // typedef替换发生在编译阶段 宏定义是在预编译阶段
  }Student;

  Student s1 = {18};
  printf("%d\n", s1.age);

  return 0;
}

文件操作

  1. FILE 结构体:我们在进行文件操作时要用到这个结构体,在我们调用fopen(const char * filename, const char * mode)时,会返回一个指向堆区的指针(这个指针指向结构体的地址,和文件没有直接关系)。这个函数会初始化结构体成员,这些成员和文件有着联系,当我们在进行文件操作时,实际上是这些成员在操作。
typedef struct{
  short level; // 缓存区‘满’或者‘空’的程度
  unsigned flags; // 文件状态标志
  char fd; // 文件描述
  unsigned char hold; // 如无缓冲区不读取字符
  short bsize; // 缓冲区大小
  unsigned char *buffer; // 数据缓冲区位置
  unsigned ar; // 指针,在文件中的指向位置
  unsigned istemp; // 临时文件指示器
  short token; // 用于有效性检查
}FILE;
  1. 使用方式
#include <stdio.h>

int main(int argc, char const *argv[])
{
  // 第二个参数为 w 表示以写操作打开文件,如果文件不存在,创建文件,
  // 如果存在,删除内容并打开
  FILE *fp = NULL;
  fp = fopen("b.txt", "w");

  if(NULL == fp){
    perror("fopen");
    return -1;
  }

  // fputs(int a, FILE *stream)
  char a = 'a';
  while(a <= 'z'){
    fputc(a, fp);
    a++;
  }

  // 关闭文件
  fclose(fp);
  fp = NULL;

  return 0;
}
  1. 标准文件输出
#include <stdio.h>

int main(int argc, char const *argv[])
{
  printf("%s\n", "zzzz"); // 标准输出(屏幕输出) zzzz

  fclose(stdout); // 关闭标准输出文件指针
  printf("%s\n", "hgz"); // 无输出
  perror("abc"); // 打印函数调用失败的原因 abc : Bad file descriptor 
  return 0;
}
  1. 按块大小读写文件fread() fwrite()
#include <stdio.h>

typedef struct Student
{
  char name[50];
  int age;
}Student;

int main(int argc, char const *argv[])
{
  Student stus[4] = {
    { "hgz1", 18 },
    { "hgz2", 19 },
    { "hgz3", 20 },
    { "hgz4", 21 }
  };
  FILE *fp = fopen("6.txt", "w");
  if(NULL == fp){
    perror("fopen");
    return -1;
  }
  int res = fwrite(stus, sizeof(stus), 1, fp);
  printf("%d\n", res);

  fclose(fp);
  fp = NULL;
  return 0;
}
#include <stdio.h>

typedef struct Student
{
  char name[50];
  int age;
}Student;

int main(int argc, char const *argv[])
{
  FILE *fp = fopen("6.txt", "r");

  Student stus[10];

  int res = fread(stus, sizeof(Student), 4, fp);

  printf("res = %d\n", res);

  for(int i = 0; i < 4; i++){
    printf("%s %d\n", stus[i].name, stus[i].age);
  }

  fclose(fp);

  return 0;
}

fread() fwrite()实现文件拷贝命令

#include <stdio.h>

int main(int argc, char const *argv[])
{
  // 模拟linux cp 命令 -> cp a.txt b.txt 将a.txt 内容复制到b.txt中

  if(argc != 3){
    printf("%d\n", argc);
    printf("argument count error:a.exe src dst\n");
    return 0;
  }
  // 如果第二个参数是 r 在windows处理二进制文件会出问题
  FILE *srcfp = fopen(argv[1], "rb"); // 源文件
  FILE *dstfp = fopen(argv[2], "wb"); // 拷贝目的文件

  char buf[4*1024];
  int len; // 每次读取的长度
  while(1){
    len = fread(buf, 1, sizeof(buf), srcfp);
    printf("len = %d\n", len);
    if(len == 0){
      break;
    }
    fwrite(buf, 1, len, dstfp);

  };

  fclose(dstfp);
  fclose(srcfp);

  return 0;
}
  1. 文件读写缓存区,在读写文件时,先把数据读写到缓冲区中。当缓存区满、调用(fclose()、fflush())等刷新缓存区函数、程序关闭时才从缓冲区读写文件。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容