C 語言學習筆記

rufo發表於2020-06-15

概述

編譯器介紹

gcc是GCC中的GUN C Compiler(C 編譯器)

g++是GCC中的GUN C++ Compiler(C++編譯器)

gcc、g++編譯常用選項說明:

選項 含義
-o file 指定生成的輸出檔名為file
-E 只進行預處理
-S(大寫) 只進行預處理和編譯
-c(小寫) 只進行預處理、編譯和彙編

gcc hello.c -o hello

沒有指定可執行程式名字,預設生成a.exe

指定可執行程式名字hello,系統自動加.exe字尾名

#include< > 與 #include “”的區別:

< > 表示系統直接按系統指定的目錄檢索

“” 表示系統先在 “” 指定的路徑查詢標頭檔案,如果找不到,再按系統指定的目錄檢索

system函式

#include <stdlib.h>

int system(const char *command);

功能:在已經執行的程式中執行另外一個外部程式

引數:外部可執行程式名字

返回值:

成功:不同系統返回值不一樣

失敗:通常是 - 1

#include <stdio.h>
#include <stdlib.h>

int main()
{
    //system("calc"); //windows平臺
    system("ls"); //Linux平臺, 需要標頭檔案#include <stdlib.h>
    return 0;
}

C程式編譯步驟

C程式碼編譯成可執行程式經過4步:

1)預處理:巨集定義展開、標頭檔案展開、條件編譯等,同時將程式碼中的註釋刪除,這裡並不會檢查語法

2)編譯:檢查語法,將預處理後檔案編譯生成彙編檔案

3)彙編:將彙編檔案生成目標檔案(二進位制檔案)

4)連結:C語言寫的程式是需要依賴各種庫的,所以編譯之後還需要把庫連結到最終的可執行程式中去

Linux平臺下,ldd(“l”為字母) 找可執行程式查所依賴的動態庫

C語言基礎

資料型別

資料型別的作用:編譯器預算物件(變數)分配的記憶體空間大小。

2016-06-01_125610

常量

在程式執行過程中,其值不能被改變的量

整型常量 100,200,-100,0
實型常量 3.14 , 0.125,-3.123
字元型常量 ‘a’,‘b’,‘1’,‘\n’
字串常量 “a”,“ab”,“12356”

變數

  • 在程式執行過程中,其值可以改變

  • 變數在使用前必須先定義,定義變數前必須有相應的資料型別

識別符號命名規則:

  • 識別符號不能是關鍵字

  • 識別符號只能由字母、數字、下劃線組成

  • 第一個字元必須為字母或下劃線

  • 識別符號中字母區分大小寫

宣告和定義區別

  • 宣告變數不需要建立儲存空間,如:extern int a;

  • 定義變數需要建立儲存空間,如:int b;

進位制

資料在計算機中主要是以補碼的形式儲存的。

術語 含義
bit(位元) 一個二進位制代表一位,一個位只能表示0或1兩種狀態。
Byte(位元組) 一個位元組為8位,計算機中儲存的最小單位是位元組。習慣以“位元組”(Byte)為單位。

十進位制轉化二進位制的方法

img

十進位制的小數轉換成二進位制

將小數部分乘以2,取結果的整數部分為二進位制的一位。 然後繼續取結果的小數部分乘2重複,一直到小數部分全部為0結束.

無標題

C語言表示相應進位制數

十進位制 以正常數字1-9開頭,如123
八進位制 以數字0開頭,如0123
十六進位制 以0x開頭,如0x123
二進位制 C語言不能直接書寫二進位制數

數值儲存方式

原碼

  • 最高位做為符號位,0表示正,為1表示負

  • 其它數值部分就是數值本身絕對值的二進位制數

十進位制數 原碼
+15 0000 1111
-15 1000 1111
+0 0000 0000
-0 1000 0000

反碼

  • 對於正數,反碼與原碼相同

  • 對於負數,符號位不變,其它部分取反(1變0,0變1)

十進位制數 反碼
+15 0000 1111
-15 1111 0000
+0 0000 0000
-0 1111 1111

補碼

在計算機系統中,數值一律用補碼來儲存。

  • 對於正數,原碼、反碼、補碼相同

  • 對於負數,其補碼為它的反碼加1

  • 補碼符號位不動,其他位求反,最後整個數加1,得到原碼

十進位制數 補碼
+15 0000 1111
-15 1111 0001
+0 0000 0000
-0 0000 0000
#include <stdio.h>

int main()
{
    int  a = -15;
    printf("%x\n", a);
    //結果為 fffffff1
    //fffffff1對應的二進位制:1111 1111 1111 1111 1111 1111 1111 0001
    //符號位不變,其它取反:1000 0000 0000 0000 0000 0000 0000 1110
    //上面加1:1000 0000 0000 0000 0000 0000 0000 1111  最高位1代表負數,就是-15
    return 0;
}

補碼的意義

在計算機系統中,數值一律用補碼來儲存,主要原因是:

  • 統一了零的編碼

  • 將符號位和其它位統一處理

  • 將減法運算轉變為加法運算

  • 兩個用補碼錶示的數相加時,如果最高位(符號位)有進位,則進位被捨棄

資料型別

整型:int

列印格式 含義
%d 輸出一個有符號的10進位制int型別
%o(字母o) 輸出8進位制的int型別
%x 輸出16進位制的int型別,字母以小寫輸出
%X 輸出16進位制的int型別,字母以大寫寫輸出
%u 輸出一個10進位制的無符號數
資料型別 佔用空間
short(短整型) 2位元組
int(整型) 4位元組
long(長整形) Windows為4位元組,Linux為4位元組(32位),8位元組(64位)
long long(長長整形) 8位元組
資料型別 佔用空間 取值範圍
short 2位元組 -32768 到 32767 (-2^15 ~ 2^15-1)
int 4位元組 -2147483648 到 2147483647 (-2^31 ~ 2^31-1)
long 4位元組 -2147483648 到 2147483647 (-2^31 ~ 2^31-1)
unsigned short 2位元組 0 到 65535 (0 ~ 2^16-1)
unsigned int 4位元組 0 到 4294967295 (0 ~ 2^32-1)
unsigned long 4位元組 0 到 4294967295 (0 ~ 2^32-1)

字元型:char

字元型變數用於儲存一個單一字元,在 C 語言中用 char 表示,其中每個字元變數都會佔用 1 個位元組。

char的本質就是一個1位元組大小的整型。

ASCII 碼大致由以下兩部分組成:

  • ASCII 非列印控制字元: ASCII 表上的數字 0-31 分配給了控制字元,用於控制像印表機等一些外圍裝置。

  • ASCII 列印字元:數字 32-126 分配給了能在鍵盤上找到的字元。數字 127 代表 Del 命令

轉義字元

轉義字元 含義 ASCII**碼值(十進位制)**
\a 警報 007
\b 退格(BS) ,將當前位置移到前一列 008
\f 換頁(FF),將當前位置移到下頁開頭 012
\n 換行(LF) ,將當前位置移到下一行開頭 010
\r 回車(CR) ,將當前位置移到本行開頭 013
\t 水平製表(HT) (跳到下一個TAB位置) 009
\v 垂直製表(VT) 011
\ \ 代表一個反斜線字元”" 092
\ ' 代表一個單引號(撇號)字元 039
\“ 代表一個雙引號字元 034
? 代表一個問號 063
\0 數字0 000

實型(浮點型):float、double

實型變數也可以稱為浮點型變數,浮點型變數是用來儲存小數數值的。

在C語言中, 浮點型變數分為兩種: 單精度浮點數(float)、 雙精度浮點數

資料型別 佔用空間 有效數字範圍
float 4位元組 7位有效數字
double 8位元組 15~16位有效數字
//不以f結尾的常量是double型別,以f結尾的常量(如3.14f)是float型別
float a = 3.14f; //或3.14F
double b = 3.14;
//科學法賦值
a = 3.2e3f;

型別限定符

限定符 含義
extern 宣告一個變數,extern宣告的變數沒有建立儲存空間。 extern int a;
const 定義一個常量,常量的值不能修改。 const int a = 10;
volatile 防止編譯器優化程式碼
register 定義暫存器變數,提高效率。CPU有空閒暫存器,那麼register就生效,如果沒有那麼register無效。

字串格式化輸出和輸入

字串常量

  • 字串是記憶體中一段連續的char空間,以’\0’結尾。

  • 字串常量是由雙引號括起來的字元序列,如“china”

  • 每個字串的結尾,編譯器會自動的新增一個結束標誌位’\0’,即 “a” 包含兩個字元’a’和’\0’。

printf函式和putchar函式

printf是輸出一個字串,putchar輸出一個char。

printf格式字元:

列印格式 對應資料型別 含義
%d int 接受整數值並將它表示為有符號的十進位制整數
%hd short int 短整數
%hu unsigned short 無符號短整數
%o unsigned int 無符號8進位制整數
%u unsigned int 無符號10進位制整數
%x,%X unsigned int 無符號16進位制整數,x對應的是abcdef,X對應的是ABCDEF
%f float 單精度浮點數
%lf double 雙精度浮點數
%e,%E double 科學計數法表示的數,此處”e”的大小寫代表在輸出時用的”e”的大小寫
%c char 字元型。可以把輸入的數字按照ASCII碼相應轉換為對應的字元
%s char * 字串。輸出字串中的字元直至字串中的空字元(’\0’即空字元)
%p void * 以16進位制形式輸出指標
%% % 輸出一個百分號

scanf函式與getchar函式

  • getchar是從標準輸入裝置讀取一個char。

  • scanf通過%轉義的方式可以得到使用者通過標準輸入裝置輸入的資料。

#include <stdio.h>

int main()
{
    char ch1;
    char ch3;

    printf("請輸入ch1的字元:");
    ch1 = getchar();
    printf("ch1 = %c\n", ch1);

    printf("請輸入ch3的字元:");
    scanf("%c", &ch3);//這裡第二個引數一定是變數的地址,而不是變數名
    printf("ch3 = %c\n", ch3);

    return 0;
}

運算子

運算子型別 作用
算術運算子 用於處理四則運算
賦值運算子 用於將表示式的值賦給變數
比較運算子 用於表示式的比較,並返回一個真值或假值
邏輯運算子 用於根據表示式的值返回真值或假值
位運算子 用於處理資料的位運算
sizeof運算子 用於求位元組數長度

邏輯運算子

c語言中的邏輯運算子都是短路運算子,一旦確定這個式子是真的就不再判斷了

運算子 術語 示例 結果
! !a 如果a為假,則!a為真; 如果a為真,則!a為假。
&& a && b 如果a和b都為真,則結果為真,否則為假。
|| a || b 如果a和b有一個為真,則結果為真,二者都為假時,結果為假。

流程結構

if…else

#include <stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    if (a > b)
    {
        printf("%d\n", a);
    }else{
        printf("%d\n", b);
    }
    return 0;
}

三目運算子

#include <stdio.h>

int main()
{
    int a = 10;
    int b = 20;
    int c;
    a = 1;
    b = 2;
    c = ( a > b ? a : b );
    printf("c2 = %d\n", c);
    return 0;
}

switch語句

#include <stdio.h>

int main()
{
    char c;
    c = getchar();
    switch (c) //引數只能是整型變數
    {
    case '1':
        printf("OK\n");
        break;//switch遇到break就中斷了
    case '2':
        printf("not OK\n");
        break;
    default://如果上面的條件都不滿足,那麼執行default
        printf("are u ok?\n");
    }
    return 0;
}

while語句

#include <stdio.h>

int main()
{
    int a = 20;
    while (a > 10)
    {
        scanf("%d", &a);
        printf("a = %d\n", a);
    }
    return 0;
}

do…while

#include <stdio.h>

int main()
{
    int a = 1;
    do
    {
        a++;
        printf("a = %d\n", a);
    } while (a < 10);

    return 0;
}

for語句

#include <stdio.h>

int main()
{
    int i;
    int sum = 0;
    for (i = 0; i <= 100; i++)
    {
        sum += i;
    }
    printf("sum = %d\n", sum);
    return 0;
}

break、continue、goto

break語句

  • 在switch條件語句和迴圈語句中都可以使用break語句:

  • 當它出現在switch條件語句中時,作用是終止某個case並跳出switch結構。

  • 當它出現在迴圈語句中,作用是跳出當前內迴圈語句,執行後面的程式碼。

continue語句

  • 在迴圈語句中,如果希望立即終止本次迴圈,並執行下一次迴圈。

  int main()
  {
      int sum = 0;           //定義變數sum
      for (int i = 1; i <= 100; i++)
      {
          if (i % 2 == 0)   //如果i是一個偶數,執行if語句中的程式碼
          {
              continue;      //結束本次迴圈
          }
          sum += i;          //實現sum和i的累加
      }

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

      return 0;
  }

goto語句

#include <stdio.h>

int main()
{
    goto End; //無條件跳轉到End的標識
    printf("aaaaaaaaa\n");

End:
    printf("bbbbbbbb\n");
    return 0;
}

陣列和字串

陣列

陣列就是在記憶體中連續的相同型別的變數空間。同一個陣列所有的成員都是相同的資料型別,同時所有的成員在記憶體中的地址是連續的

按陣列元素型別的不同,陣列可分為:數值陣列、字元陣列、指標陣列、結構陣列等類別。

int a[10];
char s[10];
char *p[10];
struct Stu boy[10];

一維陣列

int a[3]表示陣列a有3個元素,其下標從0開始計算,因此3個元素分別為a[0],a[1],a[2]
在定義陣列的同時進行賦值,稱為初始化。

  • 全域性陣列若不初始化,編譯器將其初始化為零。

  • 區域性陣列若不初始化,內容為隨機值。

    int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定義一個陣列,同時初始化所有成員變數
    int a[10] = { 1, 2, 3 };//初始化前三個成員,後面所有元素都設定為0
    int a[10] = { 0 };//所有的成員都設定為0
     //[]中不定義元素個數,定義時必須初始化
    int a[] = { 1, 2, 3, 4, 5 };//定義了一個陣列,有5個成員

一維陣列的逆置

#include <stdio.h>

int main()
{
    int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定義一個陣列,同時初始化所有成員變數
    int i = 0;
    int j = sizeof(a) / sizeof(a[0]) -1;
    int tmp;
    while (i < j)
    {
        tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
        i++;
        j--;
    }
    for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}

二維陣列

int a[3][4];定義了一個三行四列的陣列。二維陣列是按行進行存放的,先存放a[0]行,再存放a[1]行、a[2]行,並且每行有四個元素,也是依次存放的。l二維陣列實際的硬體儲存器是連續編址的,也就是說記憶體中只有一維陣列,即放完一行之後順次放入第二行,和一維陣列存放方式是一樣的。

二維陣列的初始化

    //分段賦值     int a[3][4] = {{ 1, 2, 3, 4 },{ 5, 6, 7, 8, },{ 9, 10, 11, 12 }};
    int a[3][4] = 
    { 
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8, },
        { 9, 10, 11, 12 }
    };
    //連續賦值
    int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };
    //可以只給部分元素賦初值,未初始化則為0
    int a[3][4] = { 1, 2, 3, 4  };
    //所有的成員都設定為0
    int a[3][4] = {0};
    //[]中不定義元素個數,定義時必須初始化
    int a[][4] = { 1, 2, 3, 4, 5, 6, 7, 8};

陣列名

陣列名是一個地址的常量,代表陣列中首元素的地址。

#include <stdio.h>

int main()
{
    //定義了一個二維陣列,名字叫a
    int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };

    //測二維陣列所佔記憶體空間,有3個一維陣列,每個一維陣列的空間為4*4
    //sizeof(a) = 3 * 4 * 4 = 48
    printf("sizeof(a) = %d\n", sizeof(a));

    //求二維陣列行數
    printf("i = %d\n", sizeof(a) / sizeof(a[0]));

    // 求二維陣列列數
    printf("j = %d\n", sizeof(a[0]) / sizeof(a[0][0]));

    //求二維陣列行*列總數
    printf("n = %d\n", sizeof(a) / sizeof(a[0][0]));

    return 0;
}

多維陣列

多維陣列的定義與二維陣列類似,其語法格式具體如下:int a[3][4][5];

字串

字元陣列與字串

  • C語言中沒有字串這種資料型別,可以通過char的陣列來替代;

  • 數字0(和字元‘\0’等價)結尾的char陣列就是一個字串,但如果char陣列沒有以數字0結尾,那麼就不是一個字串,只是普通字元陣列,所以字串是一種特殊的char的陣列

#include <stdio.h>

int main()
{
    char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字元陣列
    printf("c1 = %s\n", c1); //亂碼,因為沒有’\0’結束符

    //以‘\0’(‘\0’就是數字0)結尾的字元陣列是字串
    char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'}; 
    printf("c2 = %s\n", c2);

    //字串處理以‘\0’(數字0)作為結束符,後面的'h', 'l', 'l', 'e', 'o'不會輸出
    char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
    printf("c3 = %s\n", c3);

    return 0;
}

字串處理函式

gets()
#include <stdio.h>
char *gets(char *s);
功能:從標準輸入讀入字元,並儲存到s指定的記憶體空間,直到出現換行符或讀到檔案結尾為止。
引數:
    s:字串首地址
返回值:
    成功:讀入的字串
    失敗:NULL
fgets()
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:從stream指定的檔案內讀入字元,儲存到s所指定的記憶體空間,直到出現換行字元、讀到檔案結尾或是已讀了size - 1個字元為止,最後會自動加上字元 '\0' 作為字串結束。
引數:
    s:字串
    size:指定最大讀取字串的長度(size - 1)
    stream:檔案指標,如果讀鍵盤輸入的字串,固定寫為stdin
返回值:
    成功:成功讀取的字串
    讀到檔案尾或出錯: NULL

char str[100];
printf("請輸入str: ");
fgets(str, sizeof(str), stdin);
printf("str = \"%s\"\n", str);

通過scanf和gets輸入一個字串的時候,不包含結尾的“\n”,但通過fgets結尾多了“\n”。fgets()函式是安全的,不存在緩衝區溢位的問題。

puts()
#include <stdio.h>
int puts(const char *s);
功能:標準裝置輸出s字串,在輸出完成後自動輸出一個'\n'。
引數:
    s:字串首地址
返回值:
    成功:非負數
    失敗:-1
fputs()
#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:將str所指定的字串寫入到stream指定的檔案中, 字串結束符 '\0'  不寫入檔案。 
引數:
    str:字串
    stream:檔案指標,如果把字串輸出到螢幕,固定寫為stdout
返回值:
    成功:0
    失敗:-1

fputs("hello world", stdout);
strlen()
#include <string.h>
size_t strlen(const char *s);
功能:計算指定指定字串s的長度,不包含字串結束符‘\0’
引數:
s:字串首地址
返回值:字串s的長度,size_t為unsigned int型別

char str[] = "abcdefg";
int n = strlen(str);
printf("n = %d\n", n);
strcpy()
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字串複製到dest所指向的空間中,'\0'也會拷貝過去
引數:
    dest:目的字串首地址
    src:源字元首地址
返回值:
    成功:返回dest字串的首地址
    失敗:NULL
strncpy()
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字串的前n個字元複製到dest所指向的空間中,是否拷貝結束符看指定的長度是否包含'\0'。
引數:
    dest:目的字串首地址
    src:源字元首地址
    n:指定需要拷貝字串個數
返回值:
    成功:返回dest字串的首地址
    失敗:NULL

    char dest[20] ;
    char src[] = "hello world";
    strncpy(dest, src, 5);
    dest[5] = '\0';
    printf("%s\n", dest);
strcat()
#include <string.h>
char *strcat(char *dest, const char *src);
功能:將src字串連線到dest的尾部,‘\0’也會追加過去
引數:
    dest:目的字串首地址
    src:源字元首地址
返回值:
    成功:返回dest字串的首地址
    失敗:NULL

char str[20] = "123";
char *src = "hello world";
printf("%s\n", strcat(str, src));
strncat()
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:將src字串前n個字元連線到dest的尾部,‘\0’也會追加過去
引數:
    dest:目的字串首地址
    src:源字元首地址
    n:指定需要追加字串個數
返回值:
    成功:返回dest字串的首地址
    失敗:NULL

char str[20] = "123";
char *src = "hello world";
printf("%s\n", strncat(str, src, 5));
strcmp()
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比較 s1 和 s2 的大小,比較的是字元ASCII碼大小。
引數:
    s1:字串1首地址
    s2:字串2首地址
返回值:
    相等:0
    大於:>0
    小於:<0

char *str1 = "hello world";
char *str2 = "hello mike";
if (strcmp(str1, str2) == 0)
{
    printf("str1==str2\n");
}
strncmp()
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比較 s1 和 s2 前n個字元的大小,比較的是字元ASCII碼大小。
引數:
    s1:字串1首地址
    s2:字串2首地址
    n:指定比較字串的數量
返回值:
    相等:0
    大於: > 0
    小於: < 0
sprintf()
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:根據引數format字串來轉換並格式化資料,然後將結果輸出到str指定的空間中,直到出現字串結束符 '\0'  為止。
引數:
    str:字串首地址
    format:字串格式,用法和printf()一樣
返回值:
    成功:實際格式化的字元個數
    失敗:- 1

char dst[100] = { 0 };
int a = 10;
char src[] = "hello world";
int len = sprintf(dst, "a = %d, src = %s", a, src);
printf("dst = \" %s\"\n", dst);
printf("len = %d\n", len);

結果:
dst = "a = 10, src = hello world"
len = 26
strchr()
#include <string.h>
char *strchr(const char *s, int c);
功能:在字串s中查詢字母c出現的位置
引數:
    s:字串首地址
    c:匹配字母(字元)
返回值:
    成功:返回第一次出現的c地址
    失敗:NULL

char src[] = "ddda123abcd";
char *p = strchr(src, 'a');
printf("p = %s\n", p);
結果:
p = a123abcd
strstr()
#include <string.h>
char *strstr(const char *haystack, const char *needle);
功能:在字串haystack中查詢字串needle出現的位置
引數:
    haystack:源字串首地址
    needle:匹配字串首地址
返回值:
    成功:返回第一次出現的needle地址
    失敗:NULL

char src[] = "ddddabcd123abcd333abcd";
char *p = strstr(src, "abcd");
printf("p = %s\n", p);
strtok()
#include <string.h>
char *strtok(char *str, const char *delim);
功能:來將字串分割成一個個片段。當strtok()在引數s的字串中發現引數delim中包含的分割字元時, 則會將該字元改為\0 字元,當連續出現多個時只替換第一個為\0。
引數:
    str:指向欲分割的字串
    delim:為分割字串中包含的所有字元
返回值:
    成功:分割後字串首地址
    失敗:NULL
在第一次呼叫時:strtok()必需給予引數s字串,往後的呼叫則將引數s設定成NULL,每次呼叫成功則返回指向被分割出片段的指標

    char a[100] = "adc*fvcv*ebcy*hghbdfg*casdert";
    char *s = strtok(a, "*");//將"*"分割的子串取出
    while (s != NULL)
    {
        printf("%s\n", s);
        s = strtok(NULL, "*");
    }
結果:
adc
fvcv
ebcy
hghbdfg
casdert
atoi()
#include <stdlib.h>
int atoi(const char *nptr);
功能:atoi()會掃描nptr字串,跳過前面的空格字元,直到遇到數字或正負號才開始做轉換,而遇到非數字或字串結束符('\0')才結束轉換,並將結果返回返回值。
引數:
    nptr:待轉換的字串
返回值:成功轉換後整數

類似的函式有:
    atof():把一個小數形式的字串轉化為一個浮點數。
    atol():將一個字串轉化為long型別

函式

函式定義的一般形式:

返回型別 函式名(形式引數列表)

{

​ 執行語句部分;

}

在定義函式時指定的形參,在未出現函式呼叫時,它們並不佔記憶體中的儲存單元,因此稱它們是形式引數。

void max(int a = 10, int b = 20) // error, 形參不能賦值
{
}

函式的宣告

如果使用使用者自己定義的函式,而該函式與呼叫它的函式(即主調函式)不在同一檔案中,或者函式定義的位置在主調函式之後,則必須在呼叫此函式之前對被呼叫的函式作宣告。

注意:一個函式只能被定義一次,但可以宣告多次。

多檔案(分檔案)程式設計

  • 把函式宣告放在標頭檔案xxx.h中,在主函式中包含相應標頭檔案

  • 在標頭檔案對應的xxx.c中實現xxx.h宣告的函式

防止標頭檔案重複包含

方法一:

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__

// 宣告語句
#endif

方法二:

#pragma once

// 宣告語句

指標

指標的實質就是記憶體“地址”。指標是記憶體單元的編號,指標變數是存放地址的變數

  • 指標也是一種資料型別,指標變數也是一種變數

  • 指標變數指向誰,就把誰的地址賦值給指標變數

  • *操作符操作的是指標變數指向的記憶體空間

#include <stdio.h>

int main()
{
    int a = 0;
    char b = 100;
    printf("%p, %p\n", &a, &b); //列印a, b的地址

    //int*指標型別,一種資料型別,p才是變數名
    int *p;
    p = &a;//將a的地址賦值給變數p,p也是一個變數,值是一個記憶體地址編號
    printf("%d\n", *p);//p指向了a的地址,*p就是a的值
    return 0;
}

注意:&可以取得一個變數在記憶體中的地址。但是,不能取暫存器變數,因為暫存器變數不在記憶體裡,而在CPU裡面,所以是沒有地址的。

  • 使用sizeof()測量指標的大小,得到的總是:4或8

  • 在32位平臺,所有的指標(地址)都是32位(4位元組)

  • 在64位平臺,所有的指標(地址)都是64位(8位元組)

野指標和空指標

任意數值賦值給指標變數沒有意義,因為這樣的指標就成了野指標,野指標不會直接引發錯誤,操作野指標指向的記憶體區域才會出問題。

    int a = 100;
    int *p;
    p = 0x12345678; //給指標變數p賦值,p為野指標
    *p = 1000;  //操作野指標指向未知區域,記憶體出問題,err

C語言中,可以把NULL賦值給此指標,這樣就標誌此指標為空指標

NULL是一個值為0的巨集常量:#define NULL ((void *)0)

萬能指標void *

void *指標可以指向任意變數的記憶體空間

    void *p = NULL;
    int a = 10;
    p = (void *)&a; //指向變數時,最好轉換為void *
    //使用指標變數指向的記憶體時,轉換為int *
    *( (int *)p ) = 11;
    printf("a = %d\n", a);

    結果:a = 11

const修飾的指標

修飾*,指標指向記憶體區域不能修改,指標指向可以變

const int *p1 = &a; //等價於int const *p1 = &a;
//*p1 = 111; //err
p1 = &b; //ok

修飾p1,指標指向不能變,指標指向的記憶體可以修改

int * const p2 = &a;
//p2 = &b; //err
*p2 = 222; //ok

指標運算元組

陣列名字是陣列的首元素地址,它是一個常量

#include <stdio.h>

int  main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int i = 0;
    int n = sizeof(a) / sizeof(a[0]);
    //陣列遍歷操作
    for (i = 0; i < n; i++)
    {
        printf("%d, ", *(a+i));
    }
    printf("\n");
    //陣列賦值操作
    int *p = a; //定義一個指標變數儲存a的地址
    for (i = 0; i < n; i++)
    {
        p[i] = 2 * i;
    }
    return 0;
}

指標運算

  • 指標計算不是簡單的整數相加

  • 如果是一個int *,+1的結果是增加一個int的大小

  • 如果是一個char *,+1的結果是增加一個char大小

指標陣列

指標陣列,它是陣列,陣列的每個元素都是指標型別。

#include <stdio.h>

int main()
{
    int *p[3];//指標陣列
    int a = 1;
    int b = 2;
    int c = 3;
    p[0] = &a;
    p[1] = &b;
    p[2] = &c;
    for (int i = 0; i < sizeof(p) / sizeof(p[0]); i++ )
    {
        printf("%d, ", *(p[i]));
    }
    printf("\n");

    return 0;
}

多級指標

C語言允許有多級指標存在,在實際的程式中一級指標最常用,其次是二級指標。

二級指標就是指向一個一級指標變數地址的指標。

    int a = 10;
    int *p = &a; //一級指標
    *p = 100; //*p就是a

    int **q = &p;
    //*q就是p
    //**q就是a

    int ***t = &q;
    //*t就是q
    //**t就是p
    //***t就是a

指標和函式

#include <stdio.h>

void swap2(int *x, int *y)
{
    int tmp;
    tmp = *x;
    *x = *y;
    *y = tmp;
}
int main()
{
    int a = 3;
    int b = 5;
    swap2(&a, &b); //地址傳遞
    printf("a2 = %d, b2 = %d\n", a, b);
    return 0;
}

陣列名做函式引數,函式的形參會退化為指標

#include <stdio.h>

//void printArrary(int a[10], int n)
//void printArrary(int a[], int n)
void printArrary(int *a, int n)
{
    int i = 0;
    for (i = 0; i < n; i++)
    {
        printf("%d, ", a[i]);
    }
    printf("\n");
}

int main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int n = sizeof(a) / sizeof(a[0]);
    //陣列名做函式引數
    printArrary(a, n); 
    return 0;
}

指標做為函式的返回值

#include <stdio.h>
int a = 10;
int *getA()
{
    return &a;
}

int main()
{
    *( getA() ) = 111;
    printf("a = %d\n", a);
    return 0;
}

指標和字串

#include <stdio.h>

int main()
{
    char str[] = "hello world";
    char *p = str;
    *p = 'm';
    p++;
    *p = 'i';
    printf("%s\n", str);//millo world
    return 0;
}

main函式的形參

  • argv是命令列引數的字串陣列,argv陣列的每個成員都是char *型別

  • argc代表命令列引數的數量,程式名字本身算一個引數

#include <stdio.h>

//argc: 傳引數的個數(包含可執行程式)
//argv:指標陣列,指向輸入的引數
int main(int argc, char *argv[])
{
    //指標陣列,它是陣列,每個元素都是指標
    char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
    int i = 0;

    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++)
    {
        printf("%s\n", argv[i]);
    }
    return 0;
}

記憶體管理

區域性變數和全域性變數

區域性變數
區域性變數也叫auto自動變數(auto可寫可不寫),一般情況下程式碼塊{}內部定義的變數都是自動變數.

它有如下特點:

  • 在一個函式內定義,只在函式範圍內有效

  • 在複合語句中定義,只在複合語句中有效

  • 隨著函式呼叫的結束或複合語句的結束區域性變數的宣告宣告週期也結束

  • 如果沒有賦初值,內容為隨機

靜態(static)區域性變數

  • static區域性變數的作用域也是在定義的函式內有效

  • static區域性變數的生命週期和程式執行週期一樣,同時staitc區域性變數的值只初始化一次,但可以賦值多次

  • static區域性變數若未賦以初值,則由系統自動賦值:數值型變數自動賦初值0,字元型變數賦空字元

全域性變數

  • 在函式外定義,可被本檔案及其它檔案中的函式所共用,若其它檔案中的函式呼叫此變數,須用extern宣告

  • 全域性變數的生命週期和程式執行週期一樣

  • 不同檔案的全域性變數不可重名

靜態(static)全域性變數

  • 在函式外定義,作用範圍被限制在所定義的檔案中

  • 不同檔案靜態全域性變數可以重名,但作用域不衝突

  • static全域性變數的生命週期和程式執行週期一樣,同時staitc全域性變數的值只初始化一次

全域性函式和靜態函式

函式預設都是全域性的,使用關鍵字static可以將函式宣告為靜態,函式定義為static就意味著這個函式只能在定義這個函式的檔案中使用,在其他檔案中不能呼叫,即使在其他檔案中宣告這個函式都沒用。不同的檔案static函式名是可以相同的

型別 作用域 生命週期
auto變數 一對{}內 當前函式
static區域性變數 一對{}內 整個程式執行期
extern變數 整個程式 整個程式執行期
static全域性變數 當前檔案 整個程式執行期
extern函式 整個程式 整個程式執行期
static函式 當前檔案 整個程式執行期
register變數 一對{}內 當前函式

記憶體佈局

程式沒有載入到記憶體前,可執行程式內部已經分為程式碼區(text)、資料區(data)和未初始化資料區(bss)3 個部分(有些人直接把data和bss合起來叫做靜態區或全域性區

程式碼區

存放 CPU 執行的機器指令。通常程式碼區是可共享的(即另外的執行程式可以呼叫它),使其可共享的目的是對於頻繁被執行的程式,只需要在記憶體中有一份程式碼即可。程式碼區通常是隻讀的,使其只讀的原因是防止程式意外地修改了它的指令。另外,程式碼區還規劃了區域性變數的相關資訊。

全域性初始化資料區/靜態資料區(data段)

該區包含了在程式中明確被初始化的全域性變數、已經初始化的靜態變數(包括全域性靜態變數和區域性靜態變數)和常量資料(如字串常量)

未初始化資料區( bss 區)

存入的是全域性未初始化變數和未初始化靜態變數。未初始化資料區的資料在程式開始執行之前被核心初始化為 0 或者空(NULL)。

程式碼區和全域性區(data和bss)的大小就是固定的

系統把程式載入到記憶體,除了根據可執行程式的資訊分出程式碼區(text)、資料區(data)和未初始化資料區(bss)之外,還額外增加了棧區、堆區

棧區(stack)

棧是一種先進後出的記憶體結構,由編譯器自動分配釋放,存放函式的引數值、返回值、區域性變數等。在程式執行過程中實時載入和釋放,因此,區域性變數的生存週期為申請到釋放該段棧空間。

堆區(heap)

堆是一個大容器,它的容量要遠遠大於棧,但沒有棧那樣先進後出的順序。用於動態記憶體分配。堆在記憶體中位於BSS區和棧區之間。一般由程式設計師分配和釋放,若程式設計師不釋放,程式結束時由作業系統回收。

記憶體操作函式

memset()

#include <string.h>
void *memset(void *s, int c, size_t n);
功能:將s的記憶體區域的前n個位元組以引數c填入
引數:
    s:需要操作記憶體s的首地址
    c:填充的字元,c雖然引數為int,但必須是unsigned char , 範圍為0~255
    n:指定需要設定的大小
返回值:s的首地址

int a[10];
memset(a, 97, sizeof(a));
int i = 0;
for (i = 0; i < 10; i++)
{
    printf("%c\n", a[i]);
}

memcpy()

#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:拷貝src所指的記憶體內容的前n個位元組到dest所值的記憶體地址上。
引數:
    dest:目的記憶體首地址
    src:源記憶體首地址,注意:dest和src所指的記憶體空間不可重疊
    n:需要拷貝的位元組數
返回值:dest的首地址

    int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int b[10];

    memcpy(b, a, sizeof(a));
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d, ", b[i]);
    }
    printf("\n");

memmove()功能用法和memcpy()一樣,區別在於:dest和src所指的記憶體空間重疊時,memmove()仍然能處理,不過執行效率比memcpy()低些。

memcmp()

#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
功能:比較s1和s2所指向記憶體區域的前n個位元組
引數:
    s1:記憶體首地址1
    s2:記憶體首地址2
    n:需比較的前n個位元組
返回值:
    相等:=0
    大於:>0
    小於:<0

    int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    int flag = memcmp(a, b, sizeof(a));
    printf("flag = %d\n", flag);

堆區記憶體分配和釋放

malloc()

#include <stdlib.h>
void *malloc(size_t size);
功能:在記憶體的動態儲存區(堆區)中分配一塊長度為size位元組的連續區域,用來存放型別說明符指定的型別。分配的記憶體空間內容不確定,一般使用memset初始化。
引數:
    size:需要分配記憶體大小(單位:位元組)
返回值:
    成功:分配空間的起始地址
    失敗:NULL

#include <stdlib.h> 
#include <stdio.h>
#include <string.h>

int main()
{
    int count, *array, n;
    printf("請輸入要申請陣列的個數:\n");
    scanf("%d", &n);

    array = (int *)malloc(n * sizeof (int));
    //將申請到空間清0
    memset(array, 0, sizeof(int)*n);
    for (count = 0; count < n; count++) /*給陣列賦值*/
        array[count] = count;

    for (count = 0; count < n; count++) /*列印陣列元素*/
        printf("%2d", array[count]);
    //釋放array所指向的一塊記憶體空間,對同一記憶體空間多次釋放會出錯。
    free(array);
    return 0;
}

複合型別

結構體

預設對齊值:

Linux 預設#pragma pack(4)

window 預設#pragma pack(8)

結構體記憶體對齊

  1. 陣列成員對齊規則。第一個陣列成員應該放在offset為0的地方,以後每個陣列成員應該放在offset為min(當前成員的大小,#pargama pack(n))整數倍的地方開始。

  2. 結構體總的大小(sizeof的結果),必須是min(結構體內部最大成員,#pargama pack(n))的整數倍。

  3. 如果一個結構體B裡巢狀另一個結構體A,還是以最大成員型別的大小對齊,但是結構體A的起點為A內部最大成員的整數倍的地方。

struct stu2   {      
    char c;  //1    
    short s;  //2
    int i;   //4
    double d;  //8
}

image-20200413000643542

struct str
{
    char ch1;    //1位元組
    char ch2;    //1位元組
    char ch3;    //1位元組
    char ch4;    //1位元組

    int i;       //int佔4位元組,已經是4的倍數,從偏移4位元組開始,到這裡一共使用4+4=8位元組
    double d;   //double佔8位元組,從偏移8位元組開始,一共使用8+8=16位元組。
}

結構體型別和結構體變數關係:

  • 結構體型別:指定了一個結構體型別,相當於一個模型,但其中並無具體資料,系統不分配實際記憶體單元。

  • 結構體變數:系統根據結構體型別(內部成員狀況)為之分配空間。


//結構體型別的定義
struct stu
{
    char name[50];
    int age;
};
//定義型別同時定義變數
struct stu2
{
    char name[50];
    int age;
}s2 = { "lily", 22 };

//先定義型別,再定義變數(常用)struct stu s1 = { "mike", 18 };

int main()
{
    struct stu s1;

    //如果是普通變數,通過點運算子操作結構體成員
    strcpy(s1.name, "abc");
    s1.age = 18;
    printf("s1.name = %s, s1.age = %d\n", s1.name, s1.age);

    //如果是指標變數,通過->操作結構體成員
    strcpy((&s1)->name, "test");
    (&s1)->age = 22;
    printf("(&s1)->name = %s, (&s1)->age = %d\n", (&s1)->name, (&s1)->age);

    return 0;
}

結構體套結構體

struct person
{
    char name[20];
    char sex;
};

struct stu
{
    int id;
    struct person info;
};
int main()
{
    struct stu s[2] = { 1, "lily", 'F', 2, "yuri", 'M' };

    int i = 0;
    for (i = 0; i < 2; i++)
    {
        printf("id = %d\tinfo.name=%s\tinfo.sex=%c\n", s[i].id, s[i].info.name, s[i].info.sex);
    }
    return 0;
}

注意:相同型別的兩個結構體變數,可以相互賦值

結構體和指標

#include<stdio.h>

//結構體型別的定義
struct stu
{
    char name[50];
    int age;
};

int main()
{
    struct stu s1 = { "lily", 18 };
    //如果是指標變數,通過->操作結構體成員
    struct stu *p = &s1;
    printf("p->name = %s, p->age=%d\n", p->name, p->age);
    printf("(*p).name = %s, (*p).age=%d\n",  (*p).name,  (*p).age);
    //堆區結構體變數
    struct stu *p2 = NULL;
    p2 = (struct stu *)malloc(sizeof(struct  stu));
    strcpy(p2->name, "test");
    free(p2);
    return 0;
}

結構體做函式引數

結構體預設為值傳遞。

#include<stdio.h>
#include <string.h>

//結構體型別的定義
struct stu
{
    char name[50];
    int age;
};
//函式引數為結構體指標變數,
void set_stu_pro(struct stu *tmp)
{
    strcpy(tmp->name, "mike");
    tmp->age = 18;
}
int main()
{
    struct stu s = { 0 };
    set_stu_pro(&s); //地址傳遞
    printf("s.name = %s, s.age = %d\n", s.name, s.age);
    return 0;
}

共用體(聯合體)

  • 聯合union是一個能在同一個儲存空間儲存不同型別資料的型別;

  • 聯合體所佔的記憶體長度等於其最長成員的長度,也有叫做共用體;

  • 同一記憶體段可以用來存放幾種不同型別的成員,但每一瞬時只有一種起作用;

  • 共用體變數的地址和它的各成員的地址都是同一地址。

#include <stdio.h>

//共用體也叫聯合體 
union Test
{
    unsigned char a;
    unsigned int b;
    unsigned short c;
};

int main()
{
    //定義共用體變數
    union Test tmp;

    //1、所有成員的首地址是一樣的
    printf("%p, %p, %p\n", &(tmp.a), &(tmp.b), &(tmp.c));

    //2、共用體大小為最大成員型別的大小
    printf("%lu\n", sizeof(union Test));

    //3、一個成員賦值,會影響另外的成員
    //左邊是高位,右邊是低位
    //低位放低地址,高位放高地址
    tmp.b = 0x44332211;

    printf("%x\n", tmp.a); //11
    printf("%x\n", tmp.c); //2211
    return 0;
}

列舉

  • 列舉值是常量,不能在程式中用賦值語句再對它賦值。

  • 舉元素本身由系統定義了一個表示序號的數值從0開始順序定義為0,1,2 …

#include <stdio.h>
enum weekday
{
    sun = 2, mon, tue, wed, thu, fri, sat
};

enum bool
{
    flase, true
};

int main()
{
    enum weekday a, b, c;
    a = sun;
    b = mon;
    c = tue;
    printf("%d,%d,%d\n", a, b, c);//2,3,4


    enum bool flag;
    flag = true;

    if (flag == 1)
    {
        printf("flag為真\n");//flag為真
    }
    return 0;
}

typedef

typedef為C語言的關鍵字,作用是為一種資料型別(基本型別或自定義資料型別)定義一個新名字,不能建立新型別。

#include <stdio.h>

typedef int INT;
typedef char T_BYTE;

typedef struct type
{
    INT a;
    T_BYTE c;
}TYPE, *PTYPE;

int main()
{
    TYPE t;
    t.a = 254;
    t.c = 'c';
    PTYPE p = &t;
    printf("%u,%c\n", p->a, p->c);//254,c
    return 0;
}

檔案操作

從使用者或者作業系統使用的角度(邏輯上)把檔案分為:

文字檔案

  • 基於字元編碼,常見編碼有ASCII、UNICODE等

  • 一般可以使用文字編輯器直接開啟

二進位制檔案

  • 基於值編碼,自己根據具體應用,指定某個值是什麼意思

  • 把記憶體中的資料按其在記憶體中的儲存形式原樣輸出到磁碟上

在C語言中用一個指標變數指向一個檔案,這個指標稱為檔案指標。

C語言中有三個特殊的檔案指標由系統預設開啟,使用者無需定義即可直接使用:

  • stdin: 標準輸入,預設為當前終端(鍵盤),我們使用的scanf、getchar函式預設從此終端獲得資料。

  • stdout:標準輸出,預設為當前終端(螢幕),我們使用的printf、puts函式預設輸出資訊到此終端。

  • stderr:標準出錯,預設為當前終端(螢幕),我們使用的perror函式預設輸出資訊到此終端。

開啟檔案

#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);
功能:開啟檔案
引數:
    filename:需要開啟的檔名,根據需要加上路徑
    mode:開啟檔案的模式設定
返回值:
    成功:檔案指標
    失敗:NULL
    FILE *fp_passwd = NULL;

    //開啟當前目錄passdw檔案:原始檔(源程式)所在目錄
    FILE *fp_passwd = fopen("passwd.txt", "r");

    //絕對路徑:
    //開啟C盤test目錄下一個叫passwd.txt檔案
    FILE *fp_passwd  = fopen("c://test//passwd.txt","r");
開啟模式 含義
r或rb 以只讀方式開啟一個文字檔案(不建立檔案,若檔案不存在則報錯)
w或wb 以寫方式開啟檔案(如果檔案存在則清空檔案,檔案不存在則建立一個檔案)
a或ab 以追加方式開啟檔案,在末尾新增內容,若檔案不存在則建立檔案
r+或rb+ 以可讀、可寫的方式開啟檔案(不建立新檔案)
w+或wb+ 以可讀、可寫的方式開啟檔案(如果檔案存在則清空檔案,檔案不存在則建立一個檔案)
a+或ab+ 以新增方式開啟檔案,開啟檔案並在末尾更改檔案,若檔案不存在則建立檔案
  • b是二進位制模式的意思,b只是在Windows有效,在Linux用r和rb的結果是一樣的

  • Unix和Linux下所有的文字檔案行都是\n結尾,而Windows所有的文字檔案行都是\r\n結尾

檔案的關閉

#include <stdio.h>
int fclose(FILE * stream);
功能:關閉先前fopen()開啟的檔案。此動作讓緩衝區的資料寫入檔案中,並釋放系統所提供的檔案資源。
引數:
    stream:檔案指標
返回值:
    成功:0
    失敗:-1

    FILE * fp = NULL;
    fp = fopen("abc.txt", "r");
    fclose(fp);

檔案的順序讀寫

按照字元讀寫檔案fgetc、fputc

#include <stdio.h>
#include <string.h>

int main()
{
    char buf[] = "this is a test for fputc";
    int i = 0;
    int n = strlen(buf);
    FILE *fp = NULL;
    fp = fopen("abc.txt", "w");
    for (i = 0; i < n; i++)
    {
        //往檔案fp寫入字元buf[i]
        int ch = fputc(buf[i], fp);
        printf("ch = %c\n", ch);
    }
    fclose(fp);
}

在C語言中,EOF表示檔案結束符(end of file)。

#define EOF (-1)

在文字檔案中,資料都是以字元的ASCII程式碼值的形式存放。我們知道,ASCII程式碼值的範圍是0~127,不可能出現-1,因此可以用EOF作為檔案結束標誌。

#include <stdio.h>
#include <string.h>

int main()
{
    char ch;
    FILE *fp = NULL;
    fp = fopen("abc.txt", "r");
    while (!feof(fp)) //檔案沒有結束,則執行迴圈
    {
        ch = fgetc(fp);
        printf("%c", ch);
    }
    printf("\n");
    fclose(fp);
}

按照行讀寫檔案fgets、fputs

char * fgets(char * str, int size, FILE * stream);
功能:從stream指定的檔案內讀入字元,儲存到str所指定的記憶體空間,直到出現換行字元、讀到檔案結尾或是已讀了size - 1個字元為止,最後會自動加上字元 '\0' 作為字串結束。
引數:
    str:字串
    size:指定最大讀取字串的長度(size - 1)
    stream:檔案指標
返回值:
    成功:成功讀取的字串

#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:將str所指定的字串寫入到stream指定的檔案中,字串結束符'\0' 不寫入檔案。 
引數:
    str:字串
    stream:檔案指標
返回值:
    成功:0
    失敗:-1

讀寫例子:

#include <stdio.h>
#include <string.h>

int main()
{
    char buf[100] = {0};
    FILE *read = fopen("abc.txt", "r");
    FILE *write = fopen("abcd.txt", "w");
    while (!feof(read)) //檔案沒有結束
    {
        memset(buf, 0, sizeof(buf));
        char *p = fgets(buf, sizeof(buf), read);
        if (p != NULL)
        {
            fputs(buf, write);
        }
    }
}

按照格式化檔案fprintf、fscanf

//寫檔案,指定出現字串結束符 '\0' 為止
fprintf(fp, "%d %d %d\n", 1, 2, 3);
//讀檔案,從stream指定的檔案讀取字串,並根據引數format字串來轉換並格式化資料
fscanf(fp, "%d %d %d\n", &a, &b, &c);

按照塊讀寫檔案fread、fwrite

1)寫檔案
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以資料塊的方式給檔案寫入內容
引數:
    ptr:準備寫入檔案資料的地址
    size: size_t 為 unsigned int型別,此引數指定寫入檔案內容的塊資料大小
    nmemb:寫入檔案的塊數,寫入檔案資料總大小為:size * nmemb
    stream:已經開啟的檔案指標
返回值:
    成功:實際成功寫入檔案資料的塊數目,此值和nmemb相等
    失敗:0


typedef struct Stu
{
    char name[50];
    int id;
}Stu;

Stu s[3];
int i = 0;
for (i = 0; i < 3; i++)
{
    sprintf(s[i].name, "stu%d%d%d", i, i, i);
    s[i].id = i + 1;
}

int ret = fwrite(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);

2)讀檔案
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以資料塊的方式從檔案中讀取內容
引數:
    ptr:存放讀取出來資料的記憶體空間
    size: size_t 為 unsigned int型別,此引數指定讀取檔案內容的塊資料大小
    nmemb:讀取檔案的塊數,讀取檔案資料總大小為:size * nmemb
    stream:已經開啟的檔案指標
返回值:
    成功:實際成功讀取到內容的塊數,如果此值比nmemb小,但大於0,說明讀到檔案的結尾。
    失敗:0

typedef struct Stu
{
    char name[50];
    int id;
}Stu;

Stu s[3];
int ret = fread(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);

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

檔案的隨機讀寫

int fseek(FILE *stream, long offset, int whence);
功能:移動檔案流(檔案游標)的讀寫位置。
引數:
    stream:已經開啟的檔案指標
    offset:根據whence來移動的位移數(偏移量),可以是正數,也可以負數,如果正數,則相對於whence往右移動,如果是負數,則相對於whence往左移動。如果向後移動的位元組數超過了檔案末尾,再次寫入時將增大檔案尺寸。
    whence:其取值如下:
        SEEK_SET:從檔案開頭移動offset個位元組
        SEEK_CUR:從當前位置移動offset個位元組
        SEEK_END:從檔案末尾移動offset個位元組
返回值:
    成功:0
    失敗:-1
long ftell(FILE *stream);
功能:獲取檔案流(檔案游標)的讀寫位置。
引數:
    stream:已經開啟的檔案指標
返回值:
    成功:當前檔案流(檔案游標)的讀寫位置
    失敗:-1
void rewind(FILE *stream);
功能:把檔案流(檔案游標)的讀寫位置移動到檔案開頭。
引數:
    stream:已經開啟的檔案指標
返回值:
    無返回值
typedef struct Stu
{
    char name[50];
    int id;
}Stu;

Stu s[3];
Stu tmp; 
int ret = 0;

//假如已經往檔案寫入3個結構體
//fwrite(s, sizeof(Stu), 3, fp);

//檔案游標讀寫位置從開頭往右移動2個結構體的位置
fseek(fp, 2 * sizeof(Stu), SEEK_SET);

//讀第3個結構體
ret = fread(&tmp, sizeof(Stu), 1, fp);
if (ret == 1)
{
    printf("[tmp]%s, %d\n", tmp.name, tmp.id);
}

//把檔案游標移動到檔案開頭,等同於fseek(fp, 0, SEEK_SET);
rewind(fp);

ret = fread(s, sizeof(Stu), 3, fp);
printf("ret = %d\n", ret);

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

其他函式

int remove(const char *pathname);
功能:刪除檔案
引數:
    pathname:檔名
返回值:
    成功:0
    失敗:-1


int rename(const char *oldpath, const char *newpath);
功能:把oldpath的檔名改為newpath
引數:
    oldpath:舊檔名
    newpath:新檔名
返回值:
    成功:0
    失敗: - 1


#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
功能:獲取檔案狀態資訊
引數:
    path:檔名
    buf:儲存檔案資訊的結構體
返回值:
    成功:0
    失敗-1

檔案緩衝區

所謂緩衝檔案系統是指系統自動地在記憶體區為程式中每一個正在使用的檔案開闢一個檔案緩衝區從記憶體向磁碟輸出資料必須先送到記憶體中的緩衝區,裝滿緩衝區後才一起送到磁碟去。

#include <stdio.h>
int fflush(FILE *stream);
功能:更新緩衝區,讓緩衝區的資料立馬寫到檔案中。
引數:
    stream:檔案指標
返回值:
    成功:0
    失敗:-1
本作品採用《CC 協議》,轉載必須註明作者和本文連結
  • 如果可以,我要變成光

相關文章