Lecture 10 文件操作

博主不定期更新【保研/推免、C/C++、5G移動通信、Linux、生活隨筆】系列文章,喜歡的朋友【點贊+關注】支持一下吧!


Lecture 10 文件

1. 文件

1.1 格式化輸入輸出

  • printf

    %[flags][width][.prec][hlL]type

Flag 含義
- 左對齊
+ 在前面放+或-
(space) 整數留空
0 0填充
width或prec 含義
number 最小字符數(輸出寬度)
* 下一個參數是字符數
.number 小數點後的位數
.* 下一個參數是小數點後的位數
類型修飾 含義
hh 單個字節(char)
h short
l long
ll long long
L long double
type 用於 type 用於
i或d int g float
u unsignde int G float
o 八進制 a或A 十六進制浮點
x 十六進制 c char
X 字母大寫的十六進制 s 字符串
f或F float,6 p 指針
e或E 指數 n 到目前爲止讀入/寫出的個數
#include <stdio.h>

int main()
{
    int num;
	printf("%d%n\n", 123456, &num);
	printf("%d\n", num);

	return 0;
}
運行結果:
123456
6
注:因爲會造成格式化字符串漏洞的原因,目前Windows已棄用%n,因此在本地IDE上運行可能無法得到正確結果
  • scanf

    %[flag]type

flag 含義 flag 含義
* 跳過 l long, double
數字 最大字符數 ll long long
hh char L long double
h short
type 含義 type 含義
d int s 字符串
i 整數,可能十六進制或八進制 […(多種可能)] 所允許的字符
u unsigned int p 指針
o 八進制 x 十六進制
a,e,f,g float c char
  • scanf和printf的返回值
    • 讀入的項目數
    • 輸出的字符數
    • 在要求嚴格的程序中,應該判斷每次調用printf或scanf的返回值,從而瞭解程序運行中是否存在問題

1.2 文件輸入輸出

  • 用>和<做重定向
  • 用<來指定一個文件作爲輸入,用>來指定一個文件,將輸出寫入到該文件

文本文件輸入輸出FILE

  • FILE* fopen(const char *restrict path, const char *restrict mode);
  • int fclose(FILE *stream);
  • fscanf(FILE*, ...);
  • fprintf(FILE*, ...);

打開文件的標準代碼

FILE* fp = fopen("file", "r");
if (fp) 
{
    fscanf(fp, ...);
    fclose(fp);
} 
else 
{
    printf("無法打開文件");
    ...
}

fopen

rr 打開只讀
r+ 打開讀寫,從文件頭開始
w 打開只寫。如果不存在則新建,如果存在則清空
w+ 打開讀寫。如果不存在則新建,如果存在則清空
a 打開追加。如果不存在則新建,如果存在則從文件尾開始
…x (一般爲wx/ax)只新建,如果文件已存在則不能打開

1.3 二進制文件

  • 其實所有的文件最終都是二進制的
  • 文本文件無非是用最簡單的方式可以讀寫的文件(unix系統)
    • more、tail
    • cat
    • vi
  • 而二進制文件是需要專門的程序來讀寫的文件
  • 文本文件的輸入輸出是格式化,可能經過轉碼

文本 VS 二進制

  • Unix喜歡用文本文件來做數據存儲和程序配置,Windows喜歡用二進制文件
  • 文本的優勢是方便人類讀寫,而且跨平臺
  • 文本的缺點是程序輸入輸出要經過格式化,開銷大
  • 二進制的優點是程序讀寫快
  • 二進制的缺點是人類讀寫困難,而且不跨平臺
    • int的大小不一致,大小端的問題…

程序爲什麼要文件

  • 配置
    • Unix用文本,Window用註冊表
  • 數據
    • 稍微有點量的數據都放數據庫了
  • 媒體
    • 這個只能是二進制的
  • 現實是,程序通過第三方庫來讀寫文件,很少直接讀寫二進制文件了

二進制讀寫

  • size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);//第一個參數:指針,讀或寫的內存;第二個參數:這塊內存的大小(一個結構的大小);第三個參數:有幾個這樣的內存;第四個參數:文件指針
  • size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
  • 注意FILE指針是最後一個參數
  • 返回的是成功讀寫的字節數

爲什麼有nitem?

  • 因爲二進制文件的讀寫一般都是通過對一個結構變量的操作來進行的
  • 於是nitem就是用來說明這次讀寫幾個結構變量

在文件中定位

  • long ftell(FILE *stream);
  • int fseek(FILE *stream, long offset, int whence);
    • SEEK_SET:從頭開始
    • SEEK_CUR:從當前位置開始
    • SEEK_END:從尾開始(倒過來)

可移植性

  • 這樣的二進制文件不具有可移植性(int不同)
  • 解決方案之一是放棄使用int,而是typedef具有明確大小的類型
  • 更好的方案是用文本

2. 位運算

2.1 按位運算

按位運算的運算符:

  • & 按位的與

    • 讓某一位或某些位爲0:x & 0xFE
      0xFE->1111 1110,其作用爲讓x的最低位變爲0
    • 取一個數中的一段:x & 0xFF
      0xFF->0000 0000 0000 0000 0000 0000 1111 1111
      取x的最後一個字節,前面三個字節置0(這裏假設x是32位int型)
  • | 按位的或

    • 使得一位或幾個位爲1:x | 0x01
    • 把兩個數拼起來:0x00FF | 0xFF00
  • ~ 按位取反

    • 把1位變0,0位變1
    • 想得到全部位爲1的數:~0
    • 7=0111;x | 7使得低3位爲1,而x & ~7使得低3位爲0
  • ^ 按位的異或

    • 兩個位相等,結果爲0;不相等,結果爲1
    • 對一個變量用同一個值異或兩次,等於什麼也沒做
      x ^ y ^ y -> x

邏輯運算 VS 按位運算

  • 對於邏輯運算,它只看到兩個值:0和1
  • 可以認爲邏輯運算相當於把所有非0值都變成1,然後做按位運算
    • 5 & 4 -> 4 而 5 && 4 -> 1 & 1 -> 1
    • 5 | 4 -> 5 而 5 || 4 -> 1 | 1 -> 1
    • ~4 -> 3 而 !4 -> !1 -> 0

2.2 移位運算

  • << 左移
    • i << j 表示i中所有的位向左移動j個位置,而右邊填入0
    • 所有小於int的類型,移位以int的方式來做,結果是int
    • x <<= 1 \Leftrightarrow x *= 2
    • x <<= n \Leftrightarrow x *= 2n2^n
  • >> 右移
    • i >> j 表示i中所有的位向右移j位
    • 所有小於int的類型,移位以int的方式來做,結果是int
    • 對於unsigned類型,左邊填入0;對於signed類型,左邊填入原來的最高位(保持符號不變)
    • x >>= 1 \Leftrightarrow x /= 2
    • x >>= n \Leftrightarrow x /= 2n2^n
  • 移位的位數不要用負數,這是沒有定義的行爲

附:有符號十六進制數轉換爲十進制數:

首先給出原碼、反碼、補碼轉換:

原碼 \Longrightarrow 按位取反(除符號位) \Longrightarrow 反碼 \Longrightarrow 加1 \Longrightarrow 補碼

補碼的補碼是原碼

進一步可參考:原碼, 反碼, 補碼 詳解

實例:(signed) int 0x82000000

首先寫成二進制:1000 0010 0000 0000 0000 0000 0000 0000

按位取反(除符號位):1111 1101 1111 1111 1111 1111 1111 1111

加1:1111 1110 0000 0000 0000 0000 0000 0000

計算:7×167+14×166=2113929216-(7 \times 16^7+14 \times 16^6)=-2113929216


而 unsigned int 0x82000000

轉換爲十進制爲:8×167+2×166=21810380808 \times 16^7+2 \times 16^6=2181038080

2.3 位運算例子

輸出一個數的二進制

#include <stdio.h>

int main()
{
	int number;
	scanf("%d", &number);
	unsigned mask = 1u<<31;
	for ( ; mask; mask >>=1 )
	{
		printf("%d", number & mask? 1:0);//逐一判斷number的每個位上是0還是1 
	}
	printf("\n");
	
	return 0;
}

2.4 位段

把一個int的若干位組合成一個結構

struct {
    unsigned int leading : 3; //數字表示所佔比特數
    unsigned int FLAG1: 1;
    unsigned int FLAG2: 1;
    int trailing: 11;
}
  • 可以直接用位段的成員名稱來訪問
    • 比移位、與、或還方便
  • 編譯器會安排其中的位的排列,不具有可移植性
  • 當所需的位超過一個int時會採用多個int
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章