主頁 >  其他 > ??13萬字《C語言動漫對話教程(入門篇)》??(建議收藏)

??13萬字《C語言動漫對話教程(入門篇)》??(建議收藏)

2021-09-03 19:00:25 其他

您可能感興趣的文章推薦
🌞《光天化日學C語言》🌞
🧡《C語言入門100例》🧡
🌳《畫解資料結構》🌳
🌌《演算法入門指引》🌌
💜《夜深人靜寫演算法》💜

前言

??CSDN 還是以「 大學生 」 居多,能上大學的都是「 精英 」,那么我們自然要「 精益求精 」,趁著開學季,和我一起打卡學習吧!利用這個時間 「 學好一門語言 」,三年后的你自然「 不能同日而語 」
??那么這里,我整理了「 C語言的基礎語法 」大致一覽:在這里插入圖片描述
直接跳到末尾 參與投票,獲取粉絲專屬福利,

文章目錄

第一章
C語言入門

??光天化日學C語言??(01)- 第一個 C語言程式
??光天化日學C語言??(02)- 如何搭建本地環境
??光天化日學C語言??(03)- 變數
??光天化日學C語言??(04)- 格式化輸出
??光天化日學C語言??(05)- 格式化輸入
??光天化日學C語言??(06)- 進制轉換入門
??光天化日學C語言??(07)- ASCII碼
??光天化日學C語言??(08)- 常量


第二章
運算子和運算式

??光天化日學C語言??(09)- 算術運算子
??光天化日學C語言??(10)- 關系運算子
??光天化日學C語言??(11)- 邏輯運算子
??光天化日學C語言??(12)- 型別轉換
??光天化日學C語言??(13)- 位運算概覽
??光天化日學C語言??(14)- 位運算 & 的應用
??光天化日學C語言??(15)- 位運算 | 的應用
??光天化日學C語言??(16)- 位運算 ^ 的應用
??光天化日學C語言??(17)- 位運算 ~ 的應用
??光天化日學C語言??(18)- 位運算 << 的應用
??光天化日學C語言??(19)- 位運算 >> 的應用
??光天化日學C語言??(20)- 賦值運算子
??光天化日學C語言??(21)- 逗號運算子
??光天化日學C語言??(22)- 運算子優先級和結合性


第三章
資料型別的存盤方式

??光天化日學C語言??(23)- 整數的存盤
??光天化日學C語言??(24)- 浮點數的存盤
??光天化日學C語言??(25)- 浮點數的精度問題


第四章
控制流

??光天化日學C語言??(26)- if else 陳述句
??光天化日學C語言??(27)- 條件運算子
??光天化日學C語言??(28)- switch case 陳述句
??光天化日學C語言??(29)- while 陳述句
??光天化日學C語言??(30)- for 陳述句
??光天化日學C語言??(31)- break 關鍵字
??光天化日學C語言??(32)- continue 關鍵字


第五章
函式與程式結構

??光天化日學C語言??(33)- 函式


第一章
C語言入門

(01)- 第一個 C語言程式

一、C語言簡介

在這里插入圖片描述

  • C語言是一種高級語言,運行效率僅次于匯編,支持跨平臺,所以被廣泛的應用于軟體開發、系統開發、嵌入式系統、游戲開發等場景,

二、第一個C語言程式

在這里插入圖片描述

1、編程環境

  • ( 1 ) (1) (1) 百度搜索 “c語言在線編譯”,如圖四-1-1所示:

圖四-1-1

  • ( 2 ) (2) (2) 任意選擇一個在線編譯工具,我選擇的是菜鳥工具,如圖四-1-2所示:
    圖四-1-2

2、寫代碼

在這里插入圖片描述

  • 先給出代碼,然后根據行尾的標號,一行一行進行解釋;
#include <stdio.h>               // (1)
int main()                       // (2)
{
   /* 我的第一個 C 程式 */       // (3)
   printf("Hello, World! \n");   // (4)
   return 0;                     // (5)
}

這段代碼只做了一件事情,就是向螢屏上輸出一行字:Hello, World!
( 1 ) (1) (1) stdio.h是一個頭檔案 (標準輸入輸出頭檔案) , #include是一個預處理命令,用來引入頭檔案,當編譯器遇到 printf()函式時,如果沒有找到 stdio.h頭檔案,就會發生編譯錯誤,
( 2 ) (2) (2) main()作為這個程式的入口函式,代碼都是從這個函式開始執行的,
( 3 ) (3) (3)/**/包圍起來的代表注釋,是給人看到,不進行代碼的決議和執行,
( 4 ) (4) (4) printf代表將內容輸出到控制臺上,其中\n代表換行符,
( 5 ) (5) (5) 作為函式的回傳值,
在這里插入圖片描述

  • 你可能對 頭檔案、預處理命令、函式、換行符、回傳值 這些都沒有概念,沒有關系,剛開始我們不去理解這些概念,你只需要知道:通過改一些代碼以后,能夠看到想要看到的結果 就行,

3、修改代碼

  • 我們把 Hello, World 改成 光天化日學C語言 后,再來看看效果:
#include <stdio.h>                // (1)
int main()                        // (2)
{
   /* 我的第一個 C 程式 */        // (3)
   printf("光天化日學C語言! \n"); // (4)
   return 0;                      // (5)
}

  • 注意:修改完,點擊運行,就能在右邊的對話框里看到效果了,

三、編譯運行

1、編譯

  • 編譯就是把高級語言變成計算機可以識別的二進制語言,因為計算機只認識 1 和 0,你需要把一大堆復雜的語言詞法、語法全部轉換成 0 和 1,

2、運行

  • 運行就是執行可執行程式啦,就是我們通常 Windows 上的雙擊 exe 干的事情,

在這里插入圖片描述



  • 通過這一章,我們學會了如何在螢屏上輸出一行字文字,希望對你有幫助哦 ~

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第02題】給定 n,求 1 + 2 + 3 + … + n 的和 | 四種解法

(02)- 如何搭建本地環境

一、為什么要搭建本地環境

在這里插入圖片描述

  • 1)聯網:在線編譯環境畢竟涉及到聯網,如果沒有網的情況下,我們就不能寫代碼了,真是聽者傷心,聞者流淚啊;
  • 2)定制化:寫代碼是一輩子的事情,界面當然要搞得賞心悅目才能持久,本地環境可以配置字體和背景,支持定制化,覺得什么界面好看就配成什么樣的;
  • 3)代碼補全:字體高亮,代碼補全 這些好用的功能,能夠幫助你減少很多不必要編碼錯誤;
  • 4)多檔案:當代碼量比較大以后,涉及到多個檔案時,在線編譯環境就無能為力了;

二、下載 Dev C++

  • Dev C++ 是一個輕量級的 C/C++ 集成編譯環境,正因為是輕量級,所以還是有很多不太好用的地方,不過不用擔心,對于教學來說已經足夠了,
  • 相比 Visual Studio 20XX 來說,安裝快了不少,所以我打算用這個工具來進行后續文章的講解,
  • 可以選擇以下任何一個鏈接進行下載,下載后解壓出 DevCpp_v6.5.exe 即可,

百度網盤下載

  • 鏈接:C語言輕量級編譯除錯工具 Dev C++ v6.5
  • 提取碼:dd22

CSDN下載

  • 鏈接:C語言輕量級編譯除錯工具 Dev C++ v6.5

三、安裝 Dev C++

1、語言選擇

  • 雙擊 DevCpp 的 exe 檔案,會跳出如下對話框,初學者建議直接用中文,如圖五-1所示:

圖五-1

2、我接受

  • 同意安裝,如圖五-2所示:

圖五-2

3、下一步

  • 點擊下一步,如圖五-3所示:

圖五-3

4、選擇安裝位置

  • 選擇一個你鐘意的安裝路徑,點擊安裝,如圖五-4所示:

在這里插入圖片描述

圖五-4

5、看他裝完

  • 看他安裝完,大概 7 秒左右,如圖五-5-1所示:

圖五-5-1

圖五-5-2

四、配置

1、選擇語言

  • 選擇一個你鐘意的語言,推薦用中文,強我國威,壯我河山!點擊 Next,如圖六-1所示:

圖六-1

2、選擇配色

  • 選擇一個你看著舒服的配色方案,推薦 VS Code,如圖六-1所示:

在這里插入圖片描述

圖六-1

五、寫一段代碼

1、新建檔案

  • 點擊界面左上角的 【新建】 按鈕,選擇【源代碼】選單欄,如圖七-1所示;
    圖七-1

2、寫代碼

  • 把我們第一章中寫過的代碼,寫到這個檔案中,建議自己一行一行寫哦,復制粘貼 和 自己敲出來的感覺是不一樣的,
#include <stdio.h>
int main() {
    printf("光天化日寫C語言!\n");
    return 0;
}

3、保存檔案

  • 點擊選單欄的【保存】按鈕,或者 Ctrl + S 快捷鍵保存檔案,

在這里插入圖片描述

圖七-3

4、編譯運行

  • 點擊選單欄的【編譯運行】或者 F11 按鈕,就會跳出一個控制臺,如圖七-4所示:

圖七-4


  • 通過這一章,我們學會了 如何安裝一個C語言的集成環境,希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章

(03)- 變數

一、變數

在這里插入圖片描述

1、變數的定義

對于一個變數而言,有三部分組成:
??1)變數型別;
??2)變數名;
??3)變數地址;

  • 在C語言中,我們可以通過如下的方式定義一個變數:
    int Iloveyou;

1)變數型別

  • int表示變數型別,是英文單詞 Integer 的縮寫,意思是整數,

在這里插入圖片描述

2)變數名

  • Iloveyou表示變數名,也可以叫其它名字,例如:WhereIsHeroFromILoveYou1314等等,
  • 這個陳述句的含義是:在記憶體中找一塊區域,命名為Iloveyou,用它來存放整數,
  • 需要注意的是,最后有一個分號,int Iloveyou表達了一個陳述句,要用分號來結束,

在這里插入圖片描述

3)變數地址

在這里插入圖片描述

2、變數的賦值

  • C語言中可以用以下陳述句把 520 520 520 這個整數存盤到Iloveyou這個變數里:
    Iloveyou = 520;

在這里插入圖片描述

  • =在數學中叫 “等于號”,例如 1 + 1 = 2,但在C語言中,這個程序叫做變數的賦值,簡稱賦值,賦值是指把資料放到記憶體的程序,

3、變數的初始化

  • 把上面的兩個陳述句連起來,得到:
    int Iloveyou;
    Iloveyou = 520;
  • 當然,我們也可以寫成如下形式:
    int Iloveyou = 520;

在這里插入圖片描述

  • 兩段代碼的執行結果相同,都是把Iloveyou的值變成 520 520 520

在這里插入圖片描述

4、變數的由來

  • 如果我們需要,可以隨時改變它的值,如下代碼所示:
    int Iloveyou = 520;
    Iloveyou = 521;
    Iloveyou = 522;
    Iloveyou = 523;
  • 代碼執行完畢以后,它的值以最后一次賦值為準,正因為可以不斷修改,是可變的,所以才叫變數,

  • 簡單總結一下就是:資料是放在記憶體中的,變數是給這塊記憶體起的名字,有了變數就可以找到并使用這份資料,

5、多變數的定義

  • 如果幾個變數的型別一致,我們可以寫在一行上進行定義,如下:
    int x, y, z = 5;
  • 這段代碼代表一次性定義了三個整型型別的變數,并且將z初始化為 5,等價于如下代碼:
    int x; 
    int y;
    int z = 5;

6、變數間的賦值

  • 變數不能賦值給數字,但是變數可以賦值給變數,
    int a, b;
    520 = a;  // 錯誤
    a = b;    // 正確

在這里插入圖片描述

【例題1】給出如下代碼,求輸出結果是什么,

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

二、資料型別

在這里插入圖片描述

  • 接下來我們展開來講一下變數型別,更加確切的講,應該叫資料型別,C語言中有如下一些系統內置資料型別,

1、內置資料型別

  • 從上面這個表,我們可以看到,有表示字符的,有表示整數的,也有表示浮點數的,

在這里插入圖片描述

  • 先來簡單看下每種內置型別是如何進行定義的:
    char a = 'a';
    short b, c, d = 1314, e, f;
    int g = 5201314;
    long long h = 123456789;
    float i = 4.5;
    double j = 4.50000;

2、資料的大小

在這里插入圖片描述

  • 位元組是計算機中的一種基本單位,英文名為 Byte,計算機中所有的資料都是由位元組組成的,
  • 我們通常在計算機中看到的檔案單位 B 、K、M 、G、T 和位元組的關系如下:

  • 一個位元組在計算機里面是有 8 個位組成,一個位有 0 和 1 兩種狀態,所以一個位元組能表示的狀態數就是 2 8 = 256 2^8 = 256 28=256,如圖四-2-1,代表的是一個位元組的狀態,白色代表0,灰色代表1,它的二進制表示就是 ( 00001101 ) 2 (00001101)_2 (00001101)2?
    圖四-2-1

3、整數的表示范圍

  • 這樣一來,上面提到的幾種整數型別,能夠表示的整數就顯而易見了,假設位元組數為 n n n,那么能夠表示的整數個數就是能夠表示的狀態個數,即: 2 8 n 2^{8n} 28n
  • 由于我們需要表示負數 和 零,實際的每種整數資料型別能夠表示的數字范圍如下表所示:

三、變數名

1、識別符號

  • 定義變數時,我們使用了諸如 loveIloveyou這樣的名字,為了表達變數的作用,這就叫 識別符號,即 Identifier,
  • 識別符號就是程式員自己起的名字,除了變數名,后面還會講到函式名、常量名、宏名、結構體名等,它們都是識別符號,

2、關鍵字

  • 關鍵字(Keywords)是由C語言規定的具有特定意義的字串,通常也稱為保留字,例如 intcharlongintunsigned int等,
  • 程式自己定義的識別符號不能與關鍵字相同,否則會出現錯誤,
  • 后續會對各個關鍵字進行一一講解,

3、命名規則

  • ( 1 ) (1) (1) 必須由字母、數字 或者下劃線構成,如 _aaa123_都是合法的變數,?*a a#、都是非法的變數;
  • ( 2 ) (2) (2) 不能以數字開頭,如123abc不是一個合法的變數名;
  • ( 3 ) (3) (3) 大小寫敏感,即大小寫看成不同,即oO不是同一個變數;
  • ( 4 ) (4) (4) 不能將變數名和C語言的語法保留關鍵字同名;
  • ( 5 ) (5) (5) C語言雖然不限制識別符號的長度,但是它受到 編譯器 和 作業系統 的限制,例如在某個編譯器中規定識別符號前 256 位有效,當兩個識別符號前 256 位相同時,則被認為是同一個識別符號,
  • ( 6 ) (6) (6) 識別符號命名時還是最好遵循 min-length-max-infomation 的原則,即以最小的長度表達最全的資訊,不過這個是規范上的,語言層面是不會做過多的限制的,

【例題2】給出一段程式,請回答這段程式的運行結果,

#include <stdio.h>

int main()
{
	int IloveYou = 0;
	ILoveYou = 1314;
	ILoveYou = ILoveYou;
	ILoveYou = 520;
	printf("%d\n", ILoveYou);
    return 0;
}
  • 建議先看代碼,心里想著一個答案,然后再去 光天化日學C語言(01)- 第一個C語言程式 中提到的在線編譯環境中將代碼一行一行敲出來,看看和你自己想的結果是否一致,

  • 通過這一章,我們學會了 變數的定義、賦值、初始化,以及變數名命名規則,常用的資料型別,希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第03題】給定 a 和 b ,交換它們的值并輸出 | 四種解法

(04)- 格式化輸出

一、概念簡介

1、輸出的含義

2、標準輸出

在這里插入圖片描述

在C語言中,有三個函式可以用來在螢屏上輸出資料,它們分別是:
??1)puts() :只能輸出字串,并且輸出結束后會自動換行;
??2)putchar() :只能輸出單個字符;
??3)printf():可以輸出各種型別的資料,作為最靈活、最復雜、最常用的輸出函式,可以完全替代全面兩者,所以是必須掌握的,今天我們就來全面了解一下這個函式,

3、格式化

在這里插入圖片描述

  • 我們在進行輸出的時候,對于小數而言,可能需要輸出小數點后一位,亦或是兩位,這個計算機自己是不知道規則的,需要寫代碼的人告訴它,這個告訴它如何輸出的程序就被稱為格式化,

二、格式化輸出

  • printf前幾個章節都有提及,這個函式的命名含義是:Print(列印)Format (格式) ,即 格式化輸出,

1、資料型別格式化

1)整數

#include <stdio.h>
int main()
{
    int a = 520;
    long long b = 1314;
	printf("a is %d, b is %lld!\n", a, b);
	return 0;
}
  • 對于int而言,我們利用%d將要輸出的內容進行格式化,然后輸出,簡單的理解就是把%d替換為對應的變數,%lld用于對long long型別的變數進行格式化,所以這段代碼的輸出為:
a is 520, b is 1314!

在這里插入圖片描述

2)浮點數

#include <stdio.h>
int main()
{
	float f = 1.2345;
	double df = 123.45;
	printf("f is %.3f, df is %.0lf\n", f, df);
	return 0;
}
  • 對于浮點數而言,我們利用%f來對單精度浮點數float進行格式化;用%lf來對雙精度浮點數進行格式化,并且用.加 “數字” 來代表要輸出的數精確到小數點后幾位,這段代碼的輸出為:
f is 1.235, df is 123

  • 另外,單精度 和 雙精度 的區別就是雙精度的精度更高一點,也就是能夠表示的小數的范圍更加精準,這個會在介紹浮點數的存盤方式時詳細介紹,

3)字符

#include <stdio.h>
int main()
{
    char ch = 'A';
    printf("%c\n", ch);
	return 0;
}
  • 對于字符而言,我們利用%c來進行格式化;C語言中的字符是用單引號引起來的,當然,字符這個概念扯得太遠,會單獨開一個章節來講,具體可以參考 ASCII 碼,
  • 順便我們來解釋一下一直出現但是我閉口不提的換行符\n,這個符號是一個轉義符,它代表的不是兩個字符(反斜杠\和字母n),而是換行的意思;
  • 這段代碼的輸出就是一個字符 A;
A
  • 我們通過一個例題來理解這個換行符的含義;
    在這里插入圖片描述

【例題1】第1行輸出1個1,第2行輸出2個2,第3行輸出3個3,第4行輸出4個4,

#include <stdio.h>
int main()
{
    printf("1\n");
    printf("22\n");
    printf("333\n");
    printf("4444\n");
	return 0;
}
  • 我們也可以用一條陳述句解決,如下:
#include <stdio.h>
int main()
{
    printf("1\n22\n333\n4444\n");
	return 0;
}

在這里插入圖片描述

4)字串

  • 字串,是由多個字符組合而成,用雙引號引起來,這一章我不打算講得太細,只需要知道用%s進行格式化的即可,代碼如下:
#include <stdio.h>
int main()
{
    char str[100] = "I love you!";
    printf("%s\n", str);
	return 0;
}
  • 這段代碼,聰明的你應該很容易看懂啦!輸出的就是:
I love you!

在這里插入圖片描述

  • 作者:我了個擦,字體顏色都變了……

2、對齊格式化

  • 我們發現,上文中所有的格式化,都有一個%和一個字母,事實上,在百分號和字母之間,還有一些其它的內容,

主要包含如下內容:
??1)負號:如果有,則按照左對齊輸出;
??2)數字:指定欄位最小寬度,如果不足則用空格填充;
??3)小數點:用與將最小欄位寬度和精度分開;
??4)精度:用于指定字串重要列印的而最大字符數、浮點數小數點后的位數、整型最小輸出的數字數目;

在這里插入圖片描述

【例題2】給定如下一段代碼,求它的輸出內容,

#include <stdio.h>
int main()
{
    double x = 520.1314;
	int y = 520;
	
    printf("[%10.5lf]\n", x);
	printf("[%-10.5lf]\n", x);
	
	
	printf("[%10.8d]\n", y);
	printf("[%-10.8d]\n", y);

    return 0;
}

在這里插入圖片描述

  • 輸出答案如下:
[ 520.13140]
[520.13140 ]
[  00000520]
[00000520  ]
  • 我們發現,首先需要看小數點后面的部分,將要輸出的內容實際要輸出多少的長度確定下來,然后再看欄位最小寬度,最后再來看左對齊還是右對齊,

  • 然后,我們來看看把不同型別的變陣列合起來是什么效果;
#include <stdio.h>
int main()
{
    char name[100] = "Zhou";
    int old = 18;
    double meters = 1.7;
    char spostfix = 's';
    printf("My name is %s, %d years old, %.2lf meter%c.\n", 
        name, old, meters, spostfix);
    return 0;
}
  • 它的輸出結果如下:
My name is Zhou, 18 years old, 1.70 meters.

在這里插入圖片描述


  • 通過這一章,我們學會了 通過格式化的方式輸出 整數、浮點數、字符、字串,以及對資料進行對齊,希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第05題】給定 a,求 a 的絕對值

(05)- 格式化輸入

一、概念簡介

1、輸入的含義

2、標準輸入

在C語言中,有三個函式可以用來在鍵盤上輸入資料,它們分別是:
??1)gets() :用于輸入一行字串;
??2)getchar() :用于輸入單個字符;
??3)scanf():可以輸入各種型別的資料,作為最靈活、最復雜、最常用的輸入函式,雖然無法完全替代前面兩者,但是卻是必須掌握的,今天我們就來全面了解一下這個函式,

3、格式化

  • 我們在進行輸入的時候,其實都是一個字串,但是這個字串被輸入后有可能當成整數來用,也有可能還是字串,這個計算機自己是不知道規則的,需要寫代碼的人告訴它,這個告訴它如何輸入的程序就被稱為格式化,

二、整數的格式化輸入

  • scanf的函式的命名含義是:Scan(掃描)Format (格式) ,即 格式化輸入,
  • 和輸出一樣,輸入的時候,也根據資料型別的不同,分為 整數、浮點數、字符、字串等等,
  • 但是這里會有很多問題,拿整數的輸入為例,我們一個一個來看,

1、單個資料的輸入

  • 對于單個資料的輸入,如下代碼所示:
#include <stdio.h>
int main() 
{
    int a;
    scanf("%d", &a);
    printf("%d\n", a);
    return 0;
}
  • 這段代碼的執行結果如下:
13141314

其中代表回車,即我們通過鍵盤輸入1314,按下回車后,在螢屏上輸出1314

類比輸出,我們發現,輸入和輸出的差別在于:
?? ( 1 ) (1) (1) 函式名不同;
?? ( 2 ) (2) (2) 輸入少了換行符 \n
?? ( 3 ) (3) (3) 輸入多了取地址符&

  • 我們會在后面指標的章節來圍繞對這個符號進行展開的,

2、多個資料的輸入

  • 類比單個資料的輸入,我們來看看兩個資料的輸入:
#include <stdio.h>
int main() 
{
    int a, b;
    scanf("%d", &a);
    scanf("%d", &b);
    printf("%d %d\n", a, b);
    return 0;
}
  • 這段代碼的執行結果如下:
5201314520 1314

其中代表回車,即我們通過鍵盤輸入520,按下回車,再輸入1314,按下回車后,在螢屏上輸出520 1314

  • 這個很好理解,那么我們同樣可以把輸入放在一行上進行輸入,類比輸出的格式,如下:
#include <stdio.h>
int main() 
{
    int a, b;
    scanf("%d %d", &a, &b);
    printf("%d %d\n", a, b);
    return 0;
}
  • 這段代碼的執行結果如下:
520 1314520 1314

其中代表回車,即我們通過鍵盤輸入520空格1314,按下回車后,在螢屏上輸出520 1314

  • 所以,多個資料的輸入,我們可以放在一個scanf陳述句來完成,

3、空格免疫

  • 然后我們來看下,對于輸入的資料之間有一個空格和多個空格的情況,代碼如下:
#include <stdio.h>
int main()
{
    int a, b;
    scanf("%d %d", &a, &b);
    printf("%d %d\n", a, b);
    return 0;
}
520    1314520 1314

其中代表回車,即我們通過鍵盤輸入520n個空格1314,按下回車后,在螢屏上輸出520 1314

  • 也就是說,雖然文中要求是1個空格,但是我們輸入多個也不影響我們輸入,再來看下一種情況:
#include <stdio.h>
int main()
{
    int a, b;
    scanf("%d       %d", &a, &b);
    printf("%d %d\n", a, b);
    return 0;
}
520 1314520 1314

其中代表回車,即我們通過鍵盤輸入5201個空格1314,按下回車后,在螢屏上輸出520 1314

  • 也就是說,雖然文中要求多個空格,但是我們輸入1個也不影響我們輸入,

在這里插入圖片描述

4、回車結算

  • 通過以上的幾個例子,我們發現,scanf()是以回車來結算一次輸入的,
  • 用戶每次按下回車鍵,計算機就會認為完成一次輸入操作,scanf()開始讀取用戶輸入的內容,并根據我們定義好的格式化內容從中提取有效資料,只要用戶輸入的內容和格式化內容匹配,就能夠正確提取,

三、輸入緩沖區

  • 在講輸入緩沖區之前,我們先來看個例子:
#include <stdio.h>
int main()
{
    int a, b, c, d;
    scanf("%d %d %d %d", &a, &b, &c, &d);
    printf("%d %d %d %d\n", a, b, c, d);
    return 0;
}
  • 接下里我們將圍繞這段代碼進行展開,
1 2 3 41 2 3 4
  • 以上是我們的期望輸入,

1、少輸入

  • 我們嘗試少輸入1個數,按下回車后,發現程式并沒有任何的輸出,當我們再次輸入下一個數的時候,產生了正確的輸出,如下:
1 2 341 2 3 4

2、多輸入

  • 我們嘗試多輸入1個數,按下回車后,發現輸出了前四個我們輸入的數,如下:
1 2 3 4 51 2 3 4

3、再次嘗試

  • 我們增加一行代碼,就是在輸出四個數以后,再呼叫一次 scanf(),如下:
#include <stdio.h>
int main()
{
    int a, b, c, d, e;
    scanf("%d %d %d %d", &a, &b, &c, &d);
    printf("%d %d %d %d\n", a, b, c, d);
    scanf("%d", &e);
    printf("%d\n", e);
    return 0;
}
  • 然后我們采用上述的一次性輸入5個數的方式,如下:
1 2 3 4 51 2 3 4
5
  • 這時候,我們發現程式正常運行了,
  • 這是因為:我們從鍵盤輸入的資料并沒有直接交給scanf(),而是放入了輸入緩沖區中,當我們按下回車鍵,scanf()才到輸入緩沖區中讀取資料,如果緩沖區中的資料符合 scanf()給定的格式要求,那么就讀取結束;否則,繼續等待用戶輸入,或者讀取失敗,
  • 關于輸入緩沖區的內容,比較復雜,屬于進階內容,就不在這個章節繼續展開啦,

【例題1】給定一段代碼,如下,并且給出一個輸入,請問輸出是什么,

#include <stdio.h>
int main()
{
    int a = 9, b = 8, c = 7, d = 6, e = 5;
    scanf("%d %d %d %d", &a, &b, &c, &d);
    printf("%d %d %d %d\n", a, b, c, d);
    scanf("%d", &e);
    printf("%d\n", e);
    return 0;
}
  • 輸入如下:
1 2b 3 4 5

四、其他資料型別的格式化輸入

  • 其它資料型別,例如浮點數、字符、字串的格式化引數類似 printf,如下:

1、字串的輸入

  • 關于字串,后面在講完陣列以后,還會著重講,也有很多匹配演算法是應用于字串上的,也是一個很重要的內容,所以這里不作太多介紹,只需要記住,字串輸入時&可以不加,如下:
#include <stdio.h>
int main()
{
	char str[100]; 
    scanf("%s", str);    // (1)
    printf("%s\n", str);
    
    scanf("%s", &str);   // (2)
    printf("%s\n", str);
    return 0;
}
  • ( 1 ) (1) (1) ( 2 ) (2) (2) 的方式都是可以的,但是我們一般采用 ( 1 ) (1) (1) 的方式;

2、做個簡單的游戲吧

  • 這是一個算命游戲,要求根據輸入的姓名,得到這個人的算命資訊,
  • 我們先來看看效果:

在這里插入圖片描述

  • 好啦,代碼實作如下:
#include <stdio.h>
int main()
{
	char str[100]; 
	int height;
	
	printf("請大俠輸入姓名:");
    scanf("%s", str);
    
    printf("請大俠輸入身高(cm):");
	scanf("%d", &height);
	
	printf("%s大俠,身高%dcm,骨骼驚奇,是百年難得一遇的人才,只要好好學習C語言,日后必成大器!\n", str, height);
    return 0;
}
  • 你學廢了嗎?評論區留下你的算命結果哦 ~~

  • 通過這一章,我們學會了 從鍵盤輸入資料,以及實作簡單的人機互動,希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第05題】給定 a,求 a 的絕對值

(06)- 進制轉換入門

一、何為進制

  • 進制也就是 進位計數制 的簡稱,是人為定義的帶進位的計數方法,
  • 對于任何一種進制 —— X進制,表示每一個數位上的數運算時都是逢 X 進一位,
  • 例如:十進制是逢十進一,十六進制是逢十六進一,二進制就是逢二進一,八進制是逢八進一,以此類推,X進制就是 逢X進一,
  • 如圖三-1所示,代表的則是十進制的進位演示:

圖三-1

二、常用進制

1、二進制

  • 我們從定義出發:逢二進一,

兩只鞋子 = 1雙鞋子;
二個抓手 = 1雙手;

2、三進制

  • 同樣,什么是逢三進一呢?

3個月 = 1個季度;

3、四進制

  • 好了接下來,你能舉出四進制的例子嗎?

4個季度 = 1年

5、十進制

  • 當然,現實生活中遇到的最多的數字都是十進制表示,例如:0、1、2、3、… 、9、10、…

4、其它進制

七進制:7天 = 1周;
十二進制:12瓶啤酒 = 1打;
二十四進制:24小時 = 1天;
六十進制:60秒 = 1分鐘;60分鐘 = 1小時;

三、計算機中的進制

  • 在計算機中常用的進制有哪些呢?

1、二進制

  • C語言中,我們如果想表示一個二進制數,可以用0b作為前綴,然后跟上01組成的數字,我們來看一段代碼:
#include <stdio.h>
int main() {
	int a = 0b101;
	printf("%d\n", a);
	return 0;
}
  • 這段代碼中,輸出的結果如下:
5
  • 因為%d代表輸出的數是十進制,所以我們需要將二進制轉換成十進制以后輸出,0b101的數學表示如下: ( 101 ) 2 (101)_2 (101)2?
  • 它在十進制下的值為 5,
  • 因為數字比較小,所以我們可以簡單列出二進制和十進制的對應關系如下:
進制
二進制011011100101
十進制012345
  • 也就是二進制下101對應于十進制下的 5,

2、八進制

  • 講八進制之前,我們還是先來看一段代碼:
#include <stdio.h>
int main() {
	int a = 0123;
	printf("%d\n", a);
	return 0;
} 
  • 那么,這段代碼的輸出值為多少呢?

83
  • 為什么呢?參考二進制的表示法,八進制的表示法是前綴1個0,然后跟上0-7的數字;
  • 換言之,我們需要把 123這個八進制數轉換成十進制后再輸出,而轉換結果就是83,由于這里數字較大,我們已經無法一個一個數出來了,所以需要進行進制轉換,關于進制轉換,在第四節進制轉換初步里再來講解,

3、十六進制

  • 同樣的,對于十六進制數,表示方式為:以0x或者0X作為前綴,跟上0-9a-fA-F的數字,其中大小寫字母的含義相同,分別代表從1015的數字,如下表所示:
小寫字母大寫字母代表數字
aA10
bB11
cC12
dD13
eE14
fF15
  • 我們看看這段代碼的輸出:
#include <stdio.h>
int main() {
	int a = 0X123;
	printf("%d\n", a);
	return 0;
} 
  • 對于這段代碼,輸出的是:
291

在這里插入圖片描述

四、進制轉換初步

1、X進制 轉 十進制

對于 X 進制的數來說,我們定義以下幾個概念:
??【概念1】對于數字部分從右往左編號為 0 到 n n n,第 i i i 個數字位表示為 d i d_i di?,這個數字就是 d n . . . d 1 d 0 d_{n}...d_1d_0 dn?...d1?d0?
??【概念2】每個數字位有一個權值;
??【概念3】第 i i i 個數字位的權值為 X i X^i Xi

  • 基于以上幾個概念, X進制 轉 十進制的值為 每一位數字 和 它的權值的乘積的累加和,如下:
  • ∑ i = 0 n X i d i \sum_{i=0}^{n} X^id_i i=0n?Xidi?
  • ∑ \sum 是個求和符號,不必驚慌!
  • 舉個例子,對于上文提到的八進制的數0123,轉換成十進制,只需要套用公式:
  • ∑ i = 0 n X i d i = ∑ i = 0 2 8 i d i = 8 2 × 1 + 8 1 × 2 + 8 0 × 3 = 64 + 16 + 3 = 83 \begin{aligned}\sum_{i=0}^{n} X^id_i &= \sum_{i=0}^{2} 8^id_i \\ &= 8^2 \times 1 + 8^1 \times 2 + 8^0 \times 3 \\ &= 64 + 16 + 3 \\ &= 83\end{aligned} i=0n?Xidi??=i=02?8idi?=82×1+81×2+80×3=64+16+3=83?
  • 再如,上文提到的十六進制數0X123,轉換成十進制,套用同樣的公式,如下:
  • ∑ i = 0 n X i d i = ∑ i = 0 2 1 6 i d i = 1 6 2 × 1 + 1 6 1 × 2 + 1 6 0 × 3 = 256 + 32 + 3 = 291 \begin{aligned}\sum_{i=0}^{n} X^id_i &= \sum_{i=0}^{2} 16^id_i \\ &= 16^2 \times 1 + 16^1 \times 2 + 16^0 \times 3 \\ &= 256 + 32 + 3 \\ &= 291\end{aligned} i=0n?Xidi??=i=02?16idi?=162×1+161×2+160×3=256+32+3=291?

2、十進制 轉 X進制

  • 對于 十進制 轉 X進制 的問題,我們可以這么來考慮:
  • 從 X進制 轉 十進制 的原理可知,任何一個十進制數字都是由 X進制 的冪的倍數累加而成,所以,一個數一定有 X 0 X^0 X0 這部分,而這部分,可以通過原數除上 X X X 的余數得到,然后我們把原數除上 X X X 后得到的數,肯定又有 X 0 X^0 X0 的部分,就這樣重復的試除,直到得到的商為 零 時結束,程序中的余數,逆序一下就是對應進制的數了,
  • 還是一上文的例子來說,對于291我們可以通過如下方式,轉換成 十六進制,
29116  ==========3
1816  ===========2
116  ============1
  • 而對于十進制的83,我們可以通過如下方式,轉換成 八進制,
838 ============3
108 ============2
18 =============1
  • 那么,等我們后面學習了回圈陳述句以后,就可以教大家如何用計算機來實作進制轉換了,目前階段只需要了解下進制轉換的基本原理即可,

通過這一章,我們學會了:
??1)二進制的表示方式為:0b作為前綴,加上0-1組成的數字;
??2)八進制的表示方式為:0作為前綴,加上0-7組成的數字;
??3)十六進制的表示方式為:0x或者0X作為前綴,加上0-9a-fA-F組成的數字;
??4)X進制轉換成十進制;
??5)十進制轉換成X進制;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章

(07)- ASCII碼

一、ASCII 碼簡介

1、ASCII 碼的定義

  • ASCII 碼(即 American Standard Code for Information Interchange),翻譯過來是美國資訊交換標準代碼,
  • 我們一般念成 ask 2 馬,

在這里插入圖片描述

2、ASCII 碼的起源

  • 它是一套編碼系統,
  • 由于計算機用 高電平 和 低電平 分別表示 1 和 0,所以,在計算機中所有的資料在存盤和運算時都要使用二進制數表示,例如,像a-zA-Z這樣的52個字母以及0-9的數字還有一些常用的符號(例如?*#@!@#$%^&*()等)在計算機中存盤時也要使用二進制數來表示,具體用哪些二進制數字表示哪個符號,每個人都可以約定自己的一套規則,這就叫編碼,
  • 即 一個數字 和 一個字符 的一一映射,
  • 為了通信而不造成混淆,所以需要所有人都使用相同的規則,

3、ASCII 碼的表示方式

  • 標準ASCII 碼,使用 7 位二進制數(剩下的1位二進制為0)來表示所有的大寫和小寫字母,數字09、標點符號,以及在英語中使用的特殊控制字符,
  • 簡單的就可以認為:一個數字對應一個字符,具體如下表所示:

二、ASCII 碼的輸出

  • ASCII 碼對應的字符用單引號括起來,并且是可以按照兩種方式來輸出的,分別為:字符形式 和 整數形式,

1、字符

  • 當成字符用的時候,用格式化輸出%c來控制,如下:
#include <stdio.h>
int main() {
	printf("%c\n", '0');
	printf("%c\n", 'A');
	printf("%c\n", 'a');
	printf("%c\n", '$');
	return 0;
} 
  • 得到的輸出結果如下:
0
A
a
$

2、整數

  • 當成整數用的時候,用格式化輸出%d來控制,如下:
#include <stdio.h>
int main() {
	printf("%d\n", '0');
	printf("%d\n", 'A');
	printf("%d\n", 'a');
	printf("%d\n", '$');
	return 0;
} 
  • 得到的輸出結果如下:
48
65
97
36
  • 這是因為一個字符代表的是一個整數到符號的映射,它本質上還是一個整數,所以我們可以用整數的形式來輸出,字符'0'的整數編碼為48,字符'1'的整數編碼為49,以此類推,

三、ASCII 碼的運算

  • 既然當成了整數,那么就可以進行簡單的四則運算了,
  • 我們簡單來看下下面這段代碼:
#include <stdio.h>
int main() {
	printf("%c\n", '0' + 5);
	printf("%c\n", 'A' + 3);
	printf("%c\n", 'a' + 5);
	printf("%c\n", '$' + 1);
	return 0;
} 
  • 它的輸出如下:
5
D
f
%
  • 字符加上一個數字,我們可以認為是對字符編碼進行了一個對應數字的偏移,字符'0'向右偏移 5 個單位,就是字符'5';同樣的,'A'向右偏移3個單位,就是字符'D'
  • 有加法當然也有減法,接下來讓我們看個例題,

【例題1】給出如下代碼,給出它的輸出結果,

#include <stdio.h>
int main() {
	printf("%c\n", 'A' - 10);
	return 0;
}
  • 建議先想想,然后再敲代碼看看結果,是否和你想的一致,

四、ASCII 碼的比較

  • ASCII 碼既然可以和整數無縫切換,那么自然也可以進行比較了,
  • 通過上一節,我們了解到了 '0'加上1以后等于'1',那么順理成章可以得出:'0' < '1'
  • 同樣可以知道:'a' < 'b''X' < 'Y'
  • 那么,我們再來看個問題,

【例題2】請問 'a' < 'A'還是 'a' > 'A'

  • 這個問題的答案,就交給評論區吧,通過以上的教學,相信你一定能回答對,

通過這一章,我們學會了:
??1)ASCII 碼的表示;
??2)ASCII 碼的運算;
??3)ASCII 碼的比較;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第08題】給定一個字符 c,要求轉換成大寫后輸出 | ASCII碼的應用

(08)- 常量

一、常量簡介

  • C語言中的常量,主要分為以下幾種型別:

二、數值常量

  • 數值常量分為整數和浮點數,整數一般稱為整型常量,浮點數則稱為實型常量,

1、整型常量

  • 整型常量分為二進制、八進制、十進制 和 十六進制,
  • 每個整型常量分為三部分:前綴部分、數字部分、后綴部分,
  • 如下表所示:
進制前綴部分數字部分后綴部分
二進制0b0-1ulll
八進制00-7ulll
十進制0-9ulll
十六進制0x0X0-9a-fA-Fulll
  • 關于前綴這部分,在 光天化日學C語言(06)- 進制轉換入門 已經講到過,就不再累述了,
  • 這里著重提一下后綴,uunsigned)代表無符號整數,llong)代表長整型,ll代表long long

  • 換言之,無符號整型就是非負整數,
  • 待時機成熟,我會對整數的存盤結構進行一個非常詳細的介紹,

【例題1】說出以下整型常量中,哪些是非法的,為什么非法,

1314
520u
0xFoooooL
0XFeeeul
018888
0987UU
0520
0x4b
1024llul
30ll
030ul

2、實型常量

  • 實型常量又分為 小數形式 和 指數形式,

1)小數形式

  • 小數形式由三部分組成:整數部分、小數點、小數部分,例如:
	3.1415927
	4.5f
	.1314
  • 其中 f后綴代表 float,用于區分double
  • .1314等價于0.1314

2)指數形式

  • 指數形式的典型格式為xey,如下:
	1e9
	5.2e000000
	5.2e-1
	1.1e2
  • 它表示的數值是:
  • x × 1 0 y x \times 10^{y} x×10y
  • 其中 y y y 代表的是數字10的指數部分,所以是支持負數的,

三、字符常量

  • 字符常量可以是一個普通的字符、一個轉義序列,或一個通用的字符,
  • 每個字符都對應一個 ASCII 碼值,

1)普通字符

  • 普通字符就是用單引號括引起來的單個字符,如下:
	'a'
	'Q'
	'8'
	'?'
	'+'
	' '
  • 包含 26 個小寫字母,26 個大寫字母,10 個數字,幾個標點符號,運算子等等,
  • 具體參見:光天化日學C語言(07)- ASCII碼,

2)轉義字符

  • 轉義字符是用引號引起來,并且內容為 斜杠 + 字符,例如我們之前遇到的用 '\n'代表換行,\t代表水平制表符(可理解為鍵盤上的 tab 鍵),'\\'代表一個反斜杠,等等;
  • 當然還可以用 '\ooo'來代替一個字符,其中一個數字o代表一個八進制數;也可以用 '\xhh'來代表一個字符,具體見如下代碼:
#include <stdio.h>
int main() {
    char a = 65;
    char b = '\101';
    char c = '\x41';
    printf("%c %c %c\n", a, b, c);
    return 0;
} 

  • 以上的代碼輸出結果為:
A A A
  • 這是因為八進制下的101和十六進制的41在十進制下都是65,代表的是大寫字母'A'的ASCII 碼值,

【例題1】請問如何輸出一個單引號?

四、字串常量

  • 字串常量,又稱為字串字面值,是括在雙引號""中的,一個字串包含類似于字符常量的字符:普通字符、轉義序列,

1、單個字串常量

#include <stdio.h>
int main() {
    printf( "光天化日學\x43語言!\n" );
    return 0;
} 
  • 我們可以用轉義的'\x43'代表'C'和其它字符組合,變成一個字串常量,以上代碼輸出為:
光天化日學C語言!

【例題2】如果我想要如下輸出結果,請問,代碼要怎么寫?

"光天化日學C語言!"

2、字串常量分行

  • 兩個用""引起來的字串,是可以無縫連接的,如下代碼:
#include <stdio.h>
int main() {
	printf( 
		"光天化日學" 
		"C語言!\n" 
	);
	return 0;
} 
  • 這段代碼的結果也是:
光天化日學C語言!

五、符號常量

1、#define

  • 利用 #define前處理器可以定義一個常量如下:
#include <stdio.h>
#define TIPS "光天化日學\x43語言!\n" 
#define love 1314
int main() {
    printf( TIPS );
    printf("%d\n", love);
    return 0;
} 
  • 以上這段代碼,會將所有TIPS都原文替換為"光天化日學\x43語言!\n";將所有love替換為1314

2、const

  • const的用法也非常廣泛,而且涉及到很多概念,這里只介紹最簡單的用法,后面會開辟一個新的章節專門來講它的用法,
#include <stdio.h> 
const int love = 1314;
int main() {
	printf( "%d\n", love );
	return 0;
}
  • 我們可以在普通變數定義前加上const,這樣就代表它是個常量了,在整個運行程序中都不能被修改,

【例題3】下面這段代碼會發生什么情況,自己編程試一下吧,

#include <stdio.h> 
const int love = 1314;
int main() {
    love = 520;
    printf( "%d\n", love );
    return 0;
}

  • 通過這一章,我們學會了 各種型別 的常量,希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第02題】給定 n,求 1 + 2 + 3 + … + n 的和 | 四種解法

第二章
運算子和運算式

(09)- 算術運算子

一、算術運算子

  • 算術運算子主要包含以下幾個:
  • 1)四則運算子,也就是數學上所說的加減乘除;
  • 2)取余符號;
  • 3)自增和自減,
  • 那么接下來讓我們一個一個來看看吧,

1、四則運算子

  • 數學上的加減乘除和C語言的加減乘除的含義類似,但是符號表示方法不盡相同,對比如下:
/加法減法乘法除法
數學+- × \times ×÷
C語言+-*/

1)加法

  • a + b代表兩個運算元相加,代碼如下:
#include <stdio.h>
int main() {
    int a = 1, b = 2;
    double c = 1.005, d = 1.995;
    printf("a + b = %d\n", a + b );
    printf("c + d = %.3lf\n", c + d);
    printf("a + c = %.3lf\n", a + c);
    return 0;
}

  • 這段代碼的輸出為:
a + b = 3
c + d = 3.000
a + c = 2.005

2)減法

  • a - b代表從第一個運算元中減去第二個運算元,代碼如下:
#include <stdio.h>
int main() {
    int a = 1, b = 2;
    double c = 1.005, d = 1.995;
    printf("a - b = %d\n", a - b );
    printf("c - d = %.3lf\n", c - d);
    printf("a - c = %.3lf\n", a - c);
    return 0;
}
  • 這段代碼的輸出為:
a - b = -1
c - d = -0.990
a - c = -0.005

3)乘法

  • a * b代表兩個運算元相乘,代碼如下:
#include <stdio.h>
int main() {
    int a = 1, b = 2;
    double c = 1.005, d = 1.995;
    printf("a * b = %d\n",    a * b);
    printf("c * d = %.3lf\n", c * d);
    printf("a * c = %.3lf\n", a * c);
    return 0;
}

  • 這段代碼的輸出為:
a * b = 2
c * d = 2.005
a * c = 1.005

4)除法

不同型別的除數和被除數會導致不同型別的運算結果,
??1)當 除數 和 被除數 都是整數時,運算結果也是整數;
????1.a)如果能整除,結果就是它們相除的商;
????1.b)如果不能整除,那么就直接丟掉小數部分,只保留整數部分,即數學上的 取下整
??2)除數和被除數中有一個是小數,那么運算結果也是小數,并且是 double 型別的小數,

  • 我們來看一段代碼:
#include <stdio.h>
int main() {
    int a = 6, b = 3, c = 4;
    double d = 4;
    printf("a / b = %d\n", a / b );
    printf("a / c = %d\n", a / c);
    printf("a / d = %.3lf\n", a / d);
    return 0;
}
  • 輸出結果如下:
a / b = 2
a / c = 1
a / d = 1.500
  • a能被整除b,所以第一行輸出它們的商,即 2
  • a不能被整除c,所以第二行輸出它們相除的下整,即 1
  • ad中,d為浮點數,所以相除得到的也是浮點數;

#include <stdio.h>
int main() {
    int a = 5, b = 0;
    int c = a / b;
    return 0;
}
  • 這里會觸發一個例外,即 除零錯,這種情況在 C語言中是不允許的,但是由于變數的值只有在運行時才會確定,編譯器是沒辦法幫你把這個錯誤找出來的,平時寫代碼的時候一定要注意,

2、取余符號

  • 取余,也就是求余數,使用的運算子是%,C語言中的取余運算只能針對整數,也就是說,%兩邊都必須是整數,不能出現小數,否則會出現編譯錯誤,
  • 例如:5 % 3 = 27 % 2 = 1

當然,余數可以是正數也可以是負數,由%左邊的整數決定:
??1)如果%左邊是正數,那么余數也是正數;
??2)如果%左邊是負數,那么余數也是負數,

  • 我們繼續來看一段代碼:
#include <stdio.h>
int main()
{
    printf(
        "9%%4=%d\n"
        "9%%-4=%d\n"
        "-9%%4=%d\n"
        "-9%%-4=%d\n",
        9%4, 
        9%-4, 
        -9%4, 
        -9%-4
    );
    return 0;
}

  • 在 光天化日學C語言(08)- 常量 這一章中,我們提到的兩個用""引起來的字串是可以無縫連接的,所以這段代碼里面四個字串相當于一個,而%printf中是用來做格式化的,所以想要輸出到螢屏上,需要用%%,于是,我們得到輸出結果如下:
9%4=1
9%-4=1
-9%4=-1
-9%-4=-1
  • 印證了最后的符號是跟著左邊的數走的,

3、自增和自減

  • 自增和自減的情況類似,所以我們只介紹自增即可,

	x = x + 1;
  • 我們也可以寫成:
	x++;
  • 當然,也可以寫成:
	++x;
  • 這兩者的區別是什么呢?我們來看一段代碼:
#include <stdio.h>
int main()
{
    int x = 1;
    printf( "x = %d\n", x++ );
    printf( "x = %d\n", x );
    return 0;
}
  • 輸出結果是:
x = 1
x = 2
  • 這是因為x在自增前,就已經把值回傳了,所以輸出的是原值,我們再來看另一種情況:
#include <stdio.h>
int main()
{
    int x = 1;
    printf( "x = %d\n", ++x );
    printf( "x = %d\n", x );
    return 0;
}
  • 輸出結果是:
x = 2
x = 2
  • 這是因為x先進行了自增,再把值回傳,所以輸出的是自增后的值,
  • 當然,自減也是同樣的道理,大家可以自己寫代碼實踐一下,

通過這一章,我們學會了:
??1)四則運算子;
??2)取余運算子;
??3)自增和自減運算子;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

在這里插入圖片描述

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第02題】給定 n,求 1 + 2 + 3 + … + n 的和 | 四種解法
  • 【第07題】給定 n,求 1 × 2 × 3 × … × n 的乘積 | 兩種解法

(10)- 關系運算子

一、關系運算子

1、概覽

  • 關系運算子是用來判斷符號兩邊的資料的大小關系的,
  • C語言中的關系運算子主要有六個,如下:

2、表示方式

  • C語言中的關系運算子和數學中的含義相同,但是表示方法略有不同,區別如下:
關系運算子釋義C語言表示數學表示
大于>>
大于等于>=
等于===
不等于!=
小于<<
小于等于<=
  • 關系運算子的兩邊可以是變數、數值 或 運算式,例如:

1)變數

  • a > b
  • a != b

2)數值

  • 3 > 5
  • a == 6

3)運算式

  • a + b > 4
  • a > a + b

二、關系運算子的應用

1、運算結果

  • 關系運算子的運算結果只有 0 或 1,當條件成立時結果為 1,條件不成立結果為 0,
  • 我們來看一段代碼,如下:
#include <stdio.h>
int main() {
    printf("%d\n", 1 > 2);
    printf("%d\n", 1 < 2);
    return 0;
}
  • 得到的輸出結果為:
0
1
  • 原因就是1 > 2在數學上是不成立的,所以結果為0;而1 < 2在數學上是不成立的,所以結果為1

2、運算子嵌套

  • 關系運算子是允許嵌套使用的,即運算的結果可以繼續作為關系運算子的運算引數,例如以下代碼:
#include <stdio.h>
int main() {
    printf("%d\n", 1 > 2 > -1);
    return 0;
}
  • 輸出結果是多少呢?
  • 由于1 > 2的結果為0,所以1 > 2 > -1等價于0 > -1,顯然是成立的,所以輸出的結果為:
1

  • 有關于結合性的內容,會在運算子的內容都講完后,就運算子優先級和運算子結合性進行一個統一講解,現在這個階段,你只需要知道,關系運算子都是左結合,即存在多個運算子,有沒有括號的情況下,一律從左往右計算,

【例題1】給出以下代碼,問輸出的結果是什么,

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

3、運算子優先級

  • !===的優先級低于><>=<=
  • 優先級是什么呢?
  • 看個例子就能明白,
#include <stdio.h>
int main() {
    printf("%d\n", 1 < 2 == 1);
    return 0;
}
  • 我們可以做出兩種假設:
  • 假設1==優先級低于<1 < 2優先計算,則運算式等價于1 == 1,成立,輸出1
  • 假設2==優先級高于<2 == 1優先計算,則運算式等價于1 < 0,不成立,輸出0
  • 實際上,這段代碼的結果為:
1
  • ==的優先級低于<,當然,同學們可以試下 !=和其它符號的關系,
  • 另外,關系運算式會進場用在條件判斷 if陳述句中,例如:
if(a < b) {
    // TODO
}
  • 我們會在將if陳述句的時候繼續復習關系運算子相關的知識哦~

4、== 和 =

  • 初學者最容易犯的錯是把===搞混,前者是判斷相等與否,而后者是賦值,
  • 看一段代碼,就能知道:
#include <stdio.h>
int main() {
    int a = 0; 
    printf("%d\n", a = 0);
    printf("%d\n", a == 0);
    return 0;
}
  • 以上這段代碼的輸出結果是:
0
1
  • 神不神奇,意不意外?!

通過這一章,我們學會了:
??1)6種關系運算子;
??2)關系運算子的嵌套;
??3)關系運算子的優先級;

  • 希望對你有幫助哦 ~ 當然,要多寫代碼嘗試下文中提到的各種情況哦,祝大家早日成為 C 語言大神!

課后習題

  • 【第09題】給定 n 和 n 個正整數,輸出其中最大的數

(11)- 邏輯運算子

一、邏輯運算子

1、概覽

  • 邏輯運算子是用來做邏輯運算的,也就是我們數學中常說的 “與或非”,
  • C語言中的邏輯運算子主要有三個,如下:

2、表示方式

  • C語言中的邏輯運算子和數學中的含義類似,但是表示方法截然不同,對應關系如下:
邏輯運算子釋義運算元個數C語言表示數學表示
二元運算子&& ∧ \land
二元運算子|| ∨ \lor
一元運算子! ? \lnot ?
  • 二元運算子的運算元是跟在符號兩邊的,而一元運算子的運算元則是跟在符號右邊的,
  • 邏輯運算子的運算元可以是變數、數值 或 運算式,例如:

1)變數

  • a && b
  • a || b
  • !a

2)數值

  • a && 520
  • 1314 || 520
  • !0

3)運算式

  • a + b && c + d
  • a + b || c + d
  • !(a + b)

二、邏輯運算子的應用

1、運算結果

1)與運算(&&)

對于與運算,參與運算的運算元都為 “真” 時,結果才為 “真”,否則為 “假”,

#include <stdio.h>
int main() {
    printf("%d\n", 0 && 0);  // 0
    printf("%d\n", 5 && 0);  // 0
    printf("%d\n", 0 && 5);  // 0
    printf("%d\n", 5 && 9);  // 1
    return 0;
}
  • 注釋中的內容,就是實際輸出的內容,
  • 我們發現,無論運算元原本是什么,程式只關心它是 “零” 還是 “非零”,然后根據&&運算子自身的運算規則進行運算,

2)或運算(||)

對于或運算,參與運算的運算元都為“假”時,結果才為“假”,否則為“真”,

#include <stdio.h>
int main() {
    printf("%d\n", 0 || 0);  // 0
    printf("%d\n", 5 || 0);  // 1
    printf("%d\n", 0 || 5);  // 1
    printf("%d\n", 5 || 9);  // 1
    return 0;
}
  • 注釋中的內容,就是實際輸出的內容,
  • 我們同樣發現,無論運算元原本是什么,程式只關心它是 “零” 還是 “非零”,然后根據||運算子自身的運算規則進行運算,

3)非運算(!)

對于非運算,運算元為 “真”,運算結果為 “假”;運算元為 “假”,運算結果為 “真”;

#include <stdio.h>
int main() {
    printf("%d\n", !0);  // 1
    printf("%d\n", !5);  // 0
    return 0;
}
  • 注釋中的內容,就是實際輸出的內容,
  • 八個字概括:非真即假,非假即真,

2、運算子嵌套

  • 和 關系運算子 一樣,邏輯運算子也是可以支持嵌套的,即運算結果可以繼續作為邏輯運算子的運算元,例如如下代碼:
#include <stdio.h>
int main() {
    int a = !( (5 > 4) && (7 - 8) && (0 - 1) );
    printf("%d\n", a);
    return 0;
}
  • (5 > 4)(7 - 8)這兩個運算式進行與運算,等價于:1 && 1,結果為1
  • 1(0 - 1)繼續進行與運算,等價于1 && 1,結果為1
  • 1進行非運算,得到結果為 0
  • 所以這段代碼最后輸出的結果為:
0

3、運算子優先級

  • 接下來,我們看下三個運算子混合運用的情況,對于如下代碼:
#include <stdio.h>
int main() {
    int a = !( 1 || 1 && 0 );
    printf("%d\n", a);
    return 0;
}

  • 這個問題的答案是:
0

  • 我們再來看個例子,區別只是在1 || 1的兩邊加上一個括號,
#include <stdio.h>
int main() {
    int a = !( (1 || 1) && 0 );
    printf("%d\n", a);
    return 0;
}
  • 現在輸出的答案變成了:
1
  • 這是為什么呢?
  • 因為&&的優先級是比||要高的,所以在沒有任何括號的情況下,&&會優先計算,簡而言之,對于剛才的( 1 || 1 && 0 ),我們把它等價成( 1 || (1 && 0) ),這樣是不是就好理解了,
  • 用類似的方法,我們可以得到 !的優先級是最高的,所以這三個符號的優先級排序如下:
  • ∣ ∣ < & & < ! || \ < \ \&\& \ < \ ! < && < !

  • 當然,后面的章節,我們會對 算術運算子、關系運算子、邏輯運算子 等等所有的運算子的優先級 和 結合性 進行一個梳理,盡情期待 ~~

通過這一章,我們學會了:
??1)與運算:有假必假;
??2)或運算:有真必真;
??3)非運算:非真即假,非假即真;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第04題】給定 a 和 b,問 a 能否被 b 整除 | && 的應用
  • 【第11題】給出四個數,輸出四個數的和 | && 的應用

(12)- 型別轉換

一、概念

  • 型別轉換 就是將資料(即 變數、數值、運算式 等的結果)從一種型別轉換成另一種型別,
  • 今天的章節主要圍繞以下內容展開:

二、型別轉換

1、自動型別轉換

  • 這個程序不需要寫代碼的人干預,會自動發生,
  • 自動型別轉換主要發生在兩個時機:賦值 和 運算,

1)賦值

  • 將一種型別的資料賦值給另外一種型別的變數時會發生自動型別轉換,如下代碼所示:
#include <stdio.h>
int main() {
    float love = 520;
    return 0;
}
  • 這里的520原本是int型別的資料,為了賦值給love,他需要轉換成float型別,
  • 再來看另一個例子:
#include <stdio.h>
int main() {
    int loveyou = 11 / 9;
    return 0;
}
  • love / 9的值明顯不是一個整數,但是它需要賦值給int,所以需要先轉換為int型別以后,才能賦值給變數loveyou
  • 由于在賦值運算中,賦值號兩邊的資料型別不同時,需要把右邊資料的型別轉換為左邊變數的型別,這可能會導致資料失真,或者精度降低(例如上面例子中所說的浮點數轉整數,就會截掉小數部分),

2)運算

  • 在不同型別的混合運算中,編譯器也會自動地轉換資料型別,將參與運算的所有資料先轉換為同一種型別,然后再進行計算,轉換的規則如下:

轉換原則如下:
??1)資料長度短的向輸出長度長的進行轉換;
??2)精度低的向精度高的進行轉換;

  • 注意,所有的浮點運算都是以雙精度進行的,即使運算中只有float型別,也要先轉換為double型別,才能進行運算,
  • charshort參與運算時,必須先轉換成int型別,
  • 來看一個計算圓周長的例子:
#include <stdio.h>
#include <math.h>
const float PI = acos(-1.0);  // 3.1415926535...
int main(){
    int c1, r = 10;
    double c2;
    c1 = 2 * PI * r;
    c2 = 2 * PI * r;
    printf("c1=%d, c2=%lf\n", c1, c2);
    return 0;
}
  • 輸出結果為:
c1=62, c2=62.831855
  • 上述例子中,c1int型別,c2double型別,賦值號右邊的內容是計算圓的周長,完全相同,但是就是由于被賦值的變數型別不同,從而導致運算結果截然不同,
  • 雖然運算式的結果都是double型別,但由于c1int型別,所以賦值運算的結果仍為int型別,舍去了小數部分,導致資料失真,

二、強制型別轉換

  • 自動型別轉換是編譯器根據代碼的背景關系環境自行判斷的,有時候并不是那么智能,不能滿足所有的需求,所以有時候需要寫代碼的人,也就是程式員能夠自己在代碼中明確地提出要進行型別轉換,這就是強制型別轉換,

1)強制型別轉換的格式

(type_name) expression
  • 其中 type_name 為新型別名稱,expression為需要進行強制型別轉換的運算式,

2)64位整數強轉

  • 讓我們來看個非常容易犯錯的例子:
#include <stdio.h>
int main(){
    long long x = 1 << 32;
    printf("%lld\n", x); 
    return 0;
}
  • 我們想要干的事情,就是計算 2 32 2^{32} 232 并且存到變數x中,
  • 然而,這個程式的輸出結果為:
0
  • 回想一下,我們 光天化日學C語言(03)- 變數 這一節中學到的,整數的范圍最大不會超過 2 32 ? 1 2^{32}-1 232?1,所以這里顯然是超了,
  • 更加具體的原因,這里的1int型別,所以進行左移32位時,產生了溢位,所以變成了0,這里涉及到補碼相關的知識,我會在后續章節詳細進行講解,
  • 所以,我們需要先把 1 強制轉換成long long再進行左移運算,如下:
#include <stdio.h>
int main(){
    long long x = (long long)1 << 32;
    printf("%lld\n", x); 
    return 0;
}
  • 得到的結果為:
4294967296
  • 是我們期望的結果,即 2 32 2^{32} 232

3)浮點數強轉

  • 另一個比較經典的例子,就是我們計算除法的時候,如下:
#include <stdio.h>
int main(){
    int a = 10;
    int b = 3; 
    double c = a / b;
    printf("%lf\n", c); 
    return 0;
}
  • 得到的結果為:
3.000000
  • 原因是因為ab都是int型別,如果不進行干預,那么a / b的運算結果也是int型別,小數部分將被丟棄;雖然是cdouble型別,可以接收小數部分,但是在賦值之前,小數部分提前就被舍棄了,它只能接收到整數部分,這就導致除法運算的結果失真,
  • 修改方案如下:
#include <stdio.h>
int main(){
    int a = 10;
    int b = 3; 
    double c = (double) a / b;
    printf("%lf\n", c); 
    return 0;
}
  • 我們只需要將ab其中之一轉換成double,然后再參與運算即可,
  • 當然,我們還可以這么寫:
#include <stdio.h>
int main(){
    int a = 10;
    int b = 3; 
    double c = (a + 0.0) / b;
    printf("%lf\n", c); 
    return 0;
}
  • 或者這么寫:
#include <stdio.h>
int main(){
    int a = 10;
    int b = 3; 
    double c = (a * 1.0) / b;
    printf("%lf\n", c); 
    return 0;
}
  • 核心就是:不改變原有運算式的值,在其中添加一些double型別的數,使得整個運算式轉換成double
  • 使用強制型別轉換時,有時候可能不是編譯器想要的那樣,因為這是寫代碼的人自己的行為,所以程式員自己要意識到其中潛在的風險,
  • 比如將指標轉換成整型,或者將double轉換成指標,當然,有些強制轉換可能直接導致程式崩潰,

通過這一章,我們學會了:
??1)型別轉換;
??2)自動型別轉換;
??3)強制型別轉換;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第10題】給定 n 和 n 個正整數,輸出它們的平均數
  • 【第12題】給定 r,求以 r 為半徑的圓的周長和面積

(13)- 位運算概覽

一、再談二進制

  • 我們在學習 光天化日學C語言(06)- 進制轉換入門 的時候,曾經提到過二進制,

  • 在計算機中,非零即一,

1、二進制數值表示

  • 例如,在計算機中,我們可以用單純的 0 和 1 來表示數字,

1、101、1100011、100101010101 都是二進制數,
123、423424324、101020102101AF 則不是,因為有 0 和 1 以外的數字位,

  • 一般為了不產生二義性,我們會在數字的右下角寫上它的進制,例如:
  • 101 0 ( 10 ) 1010_{(10)} 1010(10)?
  • 代表的是十進制下的 1010,也就是十進制下的 “一千零一十”,
  • 101 0 ( 2 ) 1010_{(2)} 1010(2)?
  • 代表的是二進制下的 1010,也就是十進制下的 “十”,

2、二進制加法

二進制加法采用從低到高的位依次相加,當相加的和為2時,則向高位進位,

  • 例如,在二進制中,加法如下: 1 ( 2 ) + 1 ( 2 ) = 1 0 ( 2 ) 1 ( 2 ) + 0 ( 2 ) = 1 ( 2 ) 0 ( 2 ) + 1 ( 2 ) = 1 ( 2 ) 0 ( 2 ) + 0 ( 2 ) = 0 ( 2 ) 1_{(2)} + 1_{(2)} = 10_{(2)} \\ 1_{(2)} + 0_{(2)} = 1_{(2)} \\ 0_{(2)} + 1_{(2)} = 1_{(2)} \\ 0_{(2)} + 0_{(2)} = 0_{(2)} 1(2)?+1(2)?=10(2)?1(2)?+0(2)?=1(2)?0(2)?+1(2)?=1(2)?0(2)?+0(2)?=0(2)?

3、二進制減法

二進制減法采用從低到高的位依次相減,當遇到 0 減 1 的情況,則向高位借位,

  • 例如,在二進制中:減法如下: 1 ( 2 ) ? 1 ( 2 ) = 0 ( 2 ) 1 ( 2 ) ? 0 ( 2 ) = 1 ( 2 ) 1 0 ( 2 ) ? 1 ( 2 ) = 1 ( 2 ) 0 ( 2 ) ? 0 ( 2 ) = 0 ( 2 ) 1_{(2)} - 1_{(2)} = 0_{(2)} \\ 1_{(2)} - 0_{(2)} = 1_{(2)} \\ 10_{(2)} - 1_{(2)} = 1_{(2)} \\ 0_{(2)} - 0_{(2)} = 0_{(2)} 1(2)??1(2)?=0(2)?1(2)??0(2)?=1(2)?10(2)??1(2)?=1(2)?0(2)??0(2)?=0(2)?
  • 而我們今天要講的位運算正是基于二進制展開的,

二、位運算簡介

  • 位運算可以理解成對二進制數字上的每一個位進行操作的運算,
  • 位運算分為 布爾位運算子 和 移位位運算子,
  • 布爾位運算子又分為 位與(&)、位或(|)、異或(^)、按位取反(~);移位位運算子分為 左移(<<) 和 右移(>>),
  • 如圖所示:

三、位運算概覽

  • 今天,我們先來對位運算進行一個初步的介紹,后面會對每個運算子的應用做詳細介紹,包括刷題的時候如何運用位運算來加速等等,

1、布爾位運算

  • 對于布爾位運算,總共有四個,如下表所示:
C語言運算子表示含義示例
&位與x & y
|位或x | y
^異或x ^ y
~按位取反x ~ y

1)位與

  • 位與就是對運算元的每一位按照如下表格進行運算,對于每一位只有 0 或 1 兩種情況,所以組合出來總共 2 2 = 4 2^2 = 4 22=4 種情況,
左運算元右運算元結果
000
010
100
111

#include <stdio.h>
int main() {
    int a = 0b1010;           // (1)
    int b = 0b0110;           // (2)
    printf("%d\n", (a & b) ); // (3)
    return 0;
}
  • ( 1 ) (1) (1) 在C語言中,以0b作為前綴,表示這是一個二進制數,那么a的實際值就是 ( 1010 ) 2 (1010)_2 (1010)2?
  • ( 2 ) (2) (2) 同樣的,b的實際值就是 ( 0110 ) 2 (0110)_2 (0110)2?
  • ( 3 ) (3) (3) 那么這里a & b就是對 ( 1010 ) 2 (1010)_2 (1010)2? ( 0110 ) 2 (0110)_2 (0110)2? 的每一位做表格中的&運算,
  • 所以最后輸出結果為:
2
  • 因為輸出的是十進制數,它的二進制表示為: ( 0010 ) 2 (0010)_2 (0010)2?
  • 注意:這里的 前導零 可有可無,作者寫上前導零只是為了對齊以及讓讀者更加清楚位與的運算方式,

2)位或

  • 位或的運算結果如下:
左運算元右運算元結果
000
011
101
111

  • 我們來看以下這段程式:
#include <stdio.h>
int main() {
    int a = 0b1010;
    int b = 0b0110;         
    printf("%d\n", (a | b) );
    return 0;
}
  • 以上程式的輸出結果為:
14
  • 即二進制下的 ( 1110 ) 2 (1110)_2 (1110)2?

3)異或

  • 異或的運算結果如下:
左運算元右運算元結果
000
011
101
110

  • 我們來看以下這段程式:
#include <stdio.h>
int main() {
    int a = 0b1010;       
    int b = 0b0110;          
    printf("%d\n", (a ^ b) ); 
    return 0;
}
  • 以上程式的輸出結果為:
12
  • 即二進制下的 ( 1100 ) 2 (1100)_2 (1100)2?

4)按位取反

  • 按位取反其實就是 0 變 1, 1 變 0,
  • 同樣,我們來看一段程式,
#include <stdio.h>
int main() {
    int a = 0b1;
    printf("%d\n", ~a );
    return 0;
}
  • 這里我想賣個關子,同學們可以自己試一下運行結果,
  • 至于為什么會輸出這個結果,我會在 光天化日學C語言(17)- 位運算 ~ 的應用 中進行詳細講解,敬請期待,

2、移位位運算

  • 對于移位位運算,總共有兩個,如下表所示:
C語言運算子表示含義示例
<<左移x << y
>>右移x >> y

1)左移

  • 其中x << y代表將二進制的 x x x 的末尾添加 y y y 個零,就好比向左移動了 y y y 位,
  • 比如 ( 1011 ) 2 (1011)_2 (1011)2? 左移三位的結果為: ( 1011000 ) 2 (1011000)_2 (1011000)2?

2)右移

  • 其中x >> y代表將二進制的 x x x 從右邊開始截掉 y y y 個數,就好比向右移動了 y y y 位,
  • 比如 ( 101111 ) 2 (101111)_2 (101111)2? 右移三位的結果為: ( 101 ) 2 (101)_2 (101)2?

通過這一章,我們學會了:
??1)位與 & ;
??2)位或 |
??3)異或 ^;
??4)按位取反 ~;
??5)左移 <<;
??6)右移 >>;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第01題】A + B | 四種輸入方式,開啟刷題的序章
  • 【第39題】位與 & 的應用 | 一句話消除末尾連續的 1

(14)- 位運算 & 的應用

一、位與運算子

  • 位與運算子是一個二元的位運算子,也就是有兩個運算元,表示為x & y
  • 位與運算會對運算元的每一位按照如下表格進行運算,對于每一位只有 0 或 1 兩種情況,所以組合出來總共 2 2 = 4 2^2 = 4 22=4 種情況,
左運算元右運算元結果
000
010
100
111
  • 通過這個表,我們得出一些結論:
  • 1)無論是 0 或 1,只要位與上 1,還是它本身;
  • 2)無論是 0 或 1,只要位與上 0,就變成 0;
#include <stdio.h>
int main() {
    int a = 0b1010;           // (1)
    int b = 0b0110;           // (2)
    printf("%d\n", (a & b) ); // (3)
    return 0;
}
  • ( 1 ) (1) (1) 在C語言中,以0b作為前綴,表示這是一個二進制數,那么a的實際值就是 ( 1010 ) 2 (1010)_2 (1010)2?
  • ( 2 ) (2) (2) 同樣的,b的實際值就是 ( 0110 ) 2 (0110)_2 (0110)2?
  • ( 3 ) (3) (3) 那么這里a & b就是對 ( 1010 ) 2 (1010)_2 (1010)2? ( 0110 ) 2 (0110)_2 (0110)2? 的每一位做表格中的&運算,
  • 所以最后輸出結果為:
2
  • 因為輸出的是十進制數,它的二進制表示為: ( 0010 ) 2 (0010)_2 (0010)2?
  • 注意:這里的 前導零 可有可無,作者寫上前導零只是為了對齊以及讓讀者更加清楚位與的運算方式,

二、位與運算子的應用

1、奇偶性判定

  • 我們判斷一個數是奇數還是偶數,往往是通過取模%來判斷的,如下:
#include <stdio.h>
int main() {
    if(5 % 2 == 1) {
        printf("5是奇數\n");
    }
    if(6 % 2 == 0) {
        printf("6是偶數\n");
    }
    return 0;
} 
  • 然而,我們也可以這么寫:
#include <stdio.h>
int main() {
    if(5 & 1) {
        printf("5是奇數\n");
    }
    if( (6 & 1) == 0 ) {
        printf("6是偶數\n");
    }
    return 0;
} 
  • 哇,好神奇!
  • 這是利用了奇數和偶數分別的二進制數的特性,如下表所示:
-二進制末尾位
奇數1
偶數0
  • 所以,我們對任何一個數,通過將它和 0b1進行位與,結果為零,則必然這個數的二進制末尾位為0,根據以上表就能得出它是偶數了;否則,就是奇數,
  • 注意,由于if陳述句我們還沒有實際提到過,所以這里簡單提一下,后面會有系統的講解:
	if( expr ) { body }
  • 對于以上陳述句,expr代表的是一個運算式,運算式的值最后只有 零 或 非零,如果值為非零,才會執行body中的內容,

2、取末五位

【例題1】給定一個數,求它的二進制表示的末五位,以十進制輸出即可,

  • 這個問題的核心就是:我們只需要末五位,剩下的位我們是不需要的,所以可以將給定的數 位與上0b11111,這樣一來就直接得到末五位的值了,
  • 代碼實作如下:
#include <stdio.h>
int main() {
    int x;
    scanf("%d", &x);
    printf("%d\n", (x & 0b11111) );
    return 0;
} 

【例題2】如果是想得到末七位、末九位、末十四位、末 K 位,應該如何實作呢?

3、消除末尾五位

【例題3】給定一個 32 位整數,要求消除它的末五位,

  • 還是根據位與的性質,消除末五位的含義,有兩層:
  • 1)末五位,要全變成零;
  • 2)剩下的位不變;
  • 那么,根據位運算的性質,我們需要數,它的高27位都為1,低五位都為 0,則這個數就是:
  • ( 11111111111111111111111111100000 ) 2 (11111111111111111111111111100000)_2 (11111111111111111111111111100000)2?
  • 但是如果要這么寫,代碼不瘋掉,人也會瘋掉,所以一般我們把它轉成十六進制,每四個二進制位可以轉成一個十六進制數,所以得到十六進制數為0xffffffe0
  • 代碼實作如下:
#include <stdio.h>
int main() {
    int x;
    scanf("%d", &x);
    printf("%d\n", (x & 0xffffffe0) );
    return 0;
} 

4、消除末尾連續1

【例題4】給出一個整數,現在要求將這個整數轉換成二進制以后,將末尾連續的1都變成0,輸出改變后的數(以十進制輸出即可),

  • 我們知道,這個數的二進制表示形式一定是:
  • . . . 0 11...11 ? k ...0\underbrace{11...11}_{\rm k} ...0k 11...11??
  • 如果,我們把這個二進制數加上1,得到的就是:
  • . . . 1 00...00 ? k ...1\underbrace{00...00}_{\rm k} ...1k 00...00??
  • 我們把這兩個數進行位與運算,得到:
  • . . . 0 00...00 ? k ...0\underbrace{00...00}_{\rm k} ...0k 00...00??
  • 所以,你學會了嗎?

5、2的冪判定

【例題5】請用一句話,判斷一個正數是不是2的冪,

  • 如果一個數是 2 的冪,它的二進制表示必然為以下形式:
  • 1 00...00 ? k 1\underbrace{00...00}_{\rm k} 1k 00...00??
  • 這個數的十進制值為 2 k 2^k 2k
  • 那么我們將它減一,即 2 k ? 1 2^k-1 2k?1 的二進制表示如下(參考二進制減法的借位):
  • 0 11...11 ? k 0\underbrace{11...11}_{\rm k} 0k 11...11??
  • 于是 這兩個數位與的結果為零,于是我們就知道了如果一個數 x x x 是 2 的冪,那么x & (x-1)必然為零,而其他情況則不然,
  • 所以本題的答案為:
	(x & (x-1)) == 0

通過這一章,我們學會了:
??1)用位運算 & 來做奇偶性判定;
??2)用位運算 & 獲取一個數的末五位,末七位,末K位;
??3)用位運算 & 消除某些二進制位;
??4)用位運算 & 消除末尾連續 1;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第39題】位與 & 的應用 | 一句話消除末尾連續的 1

(15)- 位運算 | 的應用

一、位或運算子

  • 位或運算子是一個二元的位運算子,也就是有兩個運算元,表示為x | y
  • 位或運算會對運算元的每一位按照如下表格進行運算,對于每一位只有 0 或 1 兩種情況,所以組合出來總共 2 2 = 4 2^2 = 4 22=4 種情況,
左運算元右運算元結果
000
011
101
111
  • 通過這個表,我們得出一些結論:
  • 1)無論是 0 或 1,只要位或上 1,就變成1;
  • 2)只有當兩個運算元都是0的時候,才變成 0;

#include <stdio.h>
int main() {
    int a = 0b1010;           // (1)
    int b = 0b0110;           // (2)
    printf("%d\n", (a | b) ); // (3)
    return 0;
}
  • ( 1 ) (1) (1) 在C語言中,以0b作為前綴,表示這是一個二進制數,那么a的實際值就是 ( 1010 ) 2 (1010)_2 (1010)2?
  • ( 2 ) (2) (2) 同樣的,b的實際值就是 ( 0110 ) 2 (0110)_2 (0110)2?
  • ( 3 ) (3) (3) 那么這里a | b就是對 ( 1010 ) 2 (1010)_2 (1010)2? ( 0110 ) 2 (0110)_2 (0110)2? 的每一位做表格中的|運算,
  • 所以最后輸出結果為:
14
  • 因為輸出的是十進制數,它的二進制表示為: ( 1110 ) 2 (1110)_2 (1110)2?

二、位或運算子的應用

1、設定標記位

【例題1】給定一個數,判斷它二進制低位的第 5 位,如果為 0,則將它置為 1,

  • 這個問題,我們很容易聯想到位或,
  • 我們分析一下題目意思,如果第 5 位為 1,不用進行任何操作;如果第 5 位為 0,則置為 1,言下之意,無論第五位是什么,我們都直接置為 1即可,代碼如下:
#include <stdio.h>
int main() {
    int x;
    scanf("%d", &x);
    printf("%d\n", x | 0b10000); 
    return 0;
}

2、置空標記位

【例題2】給定一個數,判斷它二進制低位的第 5 位,如果為 1,則將它置為 0,

  • 這個問題,我們在學過 光天化日學C語言(14)- 位運算 & 的應用 以后,很容易得出這樣一種做法:
#include <stdio.h>
int main() {
    int x;
    scanf("%d", &x);
    printf("%d\n", x & 0b11111111111111111111111111101111); 
    return 0;
}
  • 其它位不能變,所以位與上1;第5位要置零,所以位與上0;
  • 這樣寫有個問題,就是這串數字太長了,一點都不美觀,而且容易寫錯,當然我們也可以轉換成 十六進制,轉換的程序也有可能出錯,
  • 而我們利用位或,只能將第5位設定成1,怎么把它設定成0呢?

我們可以配合減法來用,分成以下兩步:
??1)首先,強行將低位的第5位置成1;
??2)然后,強行將低位的第5位去掉;

  • ( 1 ) (1) (1) 步可以采用位或運算,而第 ( 2 ) (2) (2) 步,我們可以直接用減法即可,
  • 代碼實作如下:
#include <stdio.h>
int main() {
    int x;
    int a = 0b10000; 
    scanf("%d", &x);
    printf("%d\n", (x | a) - a ); 
    return 0;
}
  • 注意:直接減是不行的,因為我們首先要保證那一位為 1,否則貿然級訓產生借位,和題意不符,

3、低位連續零變一

【例題3】給定一個整數 x x x,將它低位連續的 0 都變成 1,

  • 假設這個整數低位連續有 k k k 個零,二進制表示如下:
  • . . . 1 00...00 ? k ...1\underbrace{00...00}_{\rm k} ...1k 00...00??
  • 那么,如果我們對它進行減一操作,得到的二進制數就是:
  • . . . 0 11...11 ? k ...0\underbrace{11...11}_{\rm k} ...0k 11...11??
  • 我們發現,只要對這兩個數進行位或,就能得到:
  • . . . 1 11...11 ? k ...1\underbrace{11...11}_{\rm k} ...1k 11...11??
  • 也正是題目所求,所以代碼實作如下:
#include <stdio.h>
int main() {
    int x;
    scanf("%d", &x);
    printf("%d\n", x | (x-1) );    // (1)
    return 0;
}
  • ( 1 ) (1) (1) x | (x-1)就是題目所求的 “低位連續零變一” ,

4、低位首零變一

【例題4】給定一個整數 x x x,將它低位第一個 0 變成 1,

  • 記得在評論區留下你的答案哦 ~

通過這一章,我們學會了:
??1)用位運算 | 來做標記位的設定;
??2)用位運算 | 來做標記位的清除;
??3)用位運算 | 將低位連續的零變成一;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第40題】位或 | 的應用 | 低位首零變一

(16)- 位運算 ^ 的應用

一、異或運算子

  • 異或運算子是一個二元的位運算子,也就是有兩個運算元,表示為x ^ y
  • 異或運算會對運算元的每一位按照如下表格進行運算,對于每一位只有 0 或 1 兩種情況,所以組合出來總共 2 2 = 4 2^2 = 4 22=4 種情況,
左運算元右運算元結果
000
011
101
110
  • 通過這個表,我們得出一些結論:
  • 1)兩個相同的十進制數異或的結果一定為零,
  • 2)任何一個數和 0 的異或結果一定是它本身,
  • 3)異或運算滿足結合律和交換律,

#include <stdio.h>
int main() {
    int a = 0b1010;           // (1)
    int b = 0b0110;           // (2)
    printf("%d\n", (a ^ b) ); // (3)
    return 0;
}
  • ( 1 ) (1) (1) 在C語言中,以0b作為前綴,表示這是一個二進制數,那么a的實際值就是 ( 1010 ) 2 (1010)_2 (1010)2?
  • ( 2 ) (2) (2) 同樣的,b的實際值就是 ( 0110 ) 2 (0110)_2 (0110)2?
  • ( 3 ) (3) (3) 那么這里a ^ b就是對 ( 1010 ) 2 (1010)_2 (1010)2? ( 0110 ) 2 (0110)_2 (0110)2? 的每一位做表格中的^運算,
  • 所以最后輸出結果為:
12
  • 因為輸出的是十進制數,它的二進制表示為: ( 1100 ) 2 (1100)_2 (1100)2?

二、異或運算子的應用

1、標記位取反

【例題1】給定一個數,將它的低位數起的第 4 位取反,0 變 1,1 變 0,

  • 這個問題,我們很容易聯想到異或,
  • 我們分析一下題目意思,如果第 4 位為 1,則讓它異或上 0b1000就能變成 0;如果第 4 位 為 0,則讓它異或上 0b1000就能變成 1,也就是無論如何都是異或上 0b1000,代碼如下:
#include <stdio.h>
int main() {
    int x;
    scanf("%d", &x);
    printf("%d\n", x ^ 0b1000); 
    return 0;
}

2、變數交換

【例題2】給定兩個數 a a a b b b,用異或運算交換它們的值,

  • 這個是比較老的面試題了,直接給出代碼:
#include <stdio.h>
int main() {
    int a, b;
	while (scanf("%d %d", &a, &b) != EOF) {
	    a = a ^ b;   // (1)
	    b = a ^ b;   // (2)
	    a = a ^ b;   // (3)
	    printf("%d %d\n", a, b);
	}
	return 0;
}
  • 我們直接來看 ( 1 ) (1) (1) ( 2 ) (2) (2) 這兩句話,相當于b等于a ^ b ^ b,根據異或的幾個性質,我們知道,這時候的b的值已經變成原先a的值了,
  • 而再來看第 ( 3 ) (3) (3) 句話,相當于a等于a ^ b ^ a,還是根據異或的幾個性質,這時候,a的值已經變成了原先b的值,
  • 從而實作了變數ab的交換,

3、出現奇數次的數

【例題3】輸入 n n n 個數,其中只有一個數出現了奇數次,其它所有數都出現了偶數次,求這個出現了奇數次的數,

  • 根據異或的性質,兩個一樣的數異或結果為零,也就是所有出現偶數次的數異或都為零,那么把這 n n n 個數都異或一下,得到的數就一定是一個出現奇數次的數了,
#include <stdio.h>
int main() {
    int n, x, i, ans;
    scanf("%d", &n);
    ans = 0;
    for(i = 0; i < n; ++i) {
        scanf("%d", &x);
        ans = (ans ^ x);
    } 
    printf("%d\n", ans);
    return 0;
}

4、丟失的數

【例題4】給定一個 n ? 1 n-1 n?1 個數,分別代表 1 到 n n n 的其中 n ? 1 n-1 n?1 個,求丟失的那個數,

  • 記得在評論區留下你的答案哦 ~

5、簡單加密

  • 基于 兩個數異或為零任何數和零異或為其本身 這兩個特點,異或還可以用來做簡單的加密,
  • 將明文異或上一個固定的數變成密文以后,可以通過繼續異或上這個數,再將密文轉變成明文,

通過這一章,我們學會了:
??1)用位運算 ^ 來做標記位的取反;
??2)用位運算 ^ 來做變數交換;
??3)用位運算 ^ 找出出現奇數次的數;
??4)用位運算 ^ 的加密解密;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第41題】異或 ^ 的應用 | 丟失的那個數

(17)- 位運算 ~ 的應用

一、取反運算子

  • 取反運算子是一個單目位運算子,也就是只有一個運算元,表示為~x
  • 取反運算會對運算元的每一位按照如下表格進行運算,對于每一位只有 0 或 1 兩種情況,
運算元取反結果
01
10
#include <stdio.h>
int main() {
    int a = 0b1;
    printf("%d\n", ~a );
    return 0;
}
  • 這里~a代表的是對二進制數 1 進行取反,直觀感受應該是 0,
  • 但是實際輸出的卻是:
-2
  • 這是為什么呢?
  • 那是因為,這是一個 32 位整數,實際的取反操作是這樣的:
 ~ 00000000 00000000 00000000 00000001
 --------------------------------------
   11111111 11111111 11111111 11111110
  • 32位整數的二進制表示,前導零也要參與取反,
  • 而對于一個有符號的 32 位整數,我們需要用最高位來代表符號位,即最高位為 0,則代表正數;最高位為 1,則代表負數;
  • 這時候我們就需要引入補碼的概念了,

1、補碼

  • 在計算機中,二進制編碼是采用補碼的形式表示的,補碼定義如下:

正數的補碼是它本身,符號位為 0;負數的補碼為正數數值二進制位取反后加一,符號位為一;

2、補碼舉例

  • 根據補碼的定義,-2的補碼計算,需要經過兩步:
  • 1)對 2 的二進制進行按位取反,如下:
 ~ 00000000 00000000 00000000 00000010
 --------------------------------------
   11111111 11111111 11111111 11111101
  • 2)然后加上 1,如下:
   11111111 11111111 11111111 11111101
 + 00000000 00000000 00000000 00000001
 --------------------------------------
   11111111 11111111 11111111 11111110
  • 結果正好為我們開始提到的~1的結果,

3、補碼的真實含義

  • 補碼的真實含義,其物體現在 “補” 這個字上,在數學上,兩個互為相反數的數字相加等于 0,而在計算機中,兩個互為相反數的數字相加等于 2 n 2^n 2n
  • 換言之,互為相反數的兩個數互補,補成 2 n 2^n 2n
  • 對于 32位整型, n = 32 n = 32 n=32;對于 64位整型, n = 64 n = 64 n=64,所以補碼也可以表示成如下形式:
  • [ x ] 補 = { x ( 0 ≤ x < 2 n ? 1 ) 2 n + x ( ? 2 n ? 1 ≤ x < 0 ) [x]_補 = \begin{cases}x & (0 \le x \lt 2^{n-1})\\ 2^{n} + x & (-2^{n-1} \le x \lt 0)\\ \end{cases} [x]?={x2n+x?(0x<2n?1)(?2n?1x<0)?
  • 于是,對于int型別,就有:
  • x + ( ? x ) = 2 32 x + (-x) = 2^{32} x+(?x)=232
  • 因此, ? 2 = 2 32 ? 2 -2 = 2^{32} - 2 ?2=232?2
  • 于是,我們開始數數……
2^32        = 1 00000000 00000000 00000000 00000000
2^32 - 1    =   11111111 11111111 11111111 11111111
2^32 - 2    =   11111111 11111111 11111111 11111110
...
  • 近一步了解了-2的二進制表示,
  • 關于補碼的深入內容,詳細可以參考這篇文章:《C/C++ 面試 100 例》(九)補碼全網最全總結,

二、取反運算子的應用

1、0 的取反

【例題1】0 的取反結果為多少呢?

  • 首先對原始碼進行取反,得到:
 ~ 00000000 00000000 00000000 00000000
 --------------------------------------
   11111111 11111111 11111111 11111111
  • 這個問題,我們剛討論完,這個答案為 2 32 ? 1 2^{32}-1 232?1,但是實際輸出時,你會發現,它的值是-1
  • 這是為什么?
  • 搞得我一頭霧水,
  • 原因是因為在C語言中有兩種型別的int,分別為unsigned intsigned int,我們之前討論的int都是signed int的簡稱,

1)有符號整型

  • 對于有符號整型signed int而言,最高位表示符號位,所以只有31位能表示數值,能夠表示的數值范圍是: ? 2 31 ≤ x ≤ 2 31 ? 1 -2^{31} \le x \le 2^{31}-1 ?231x231?1
  • 所以,對于有符號整型,輸出采用%d,如下:
#include <stdio.h>
int main() {
    printf("%d\n", ~0 );
    return 0;
}
  • 結果為:
-1

2)無符號整型

  • 對于無符號整型unsigned int而言,由于不需要符號位,所以總共有32位表示數值,數值范圍為:
  • 0 ≤ x ≤ 2 32 ? 1 0 \le x \le 2^{32}-1 0x232?1
  • 對于無符號整型,輸出采用%u,如下:
#include <stdio.h>
int main() {
    printf("%u\n", ~0 );
    return 0;
}
  • 結果為:
4294967295
  • 2 32 ? 1 2^{32}-1 232?1

2、相反數

【例題2】給定一個int型別的正數 x x x,求 x x x 的相反數(注意:不能用負號),

  • 這里,我們可以直接利用補碼的定義,對于正數 x x x,它的相反數的補碼就是 x x x 二進制取反加一,即:~x + 1
#include <stdio.h>
int main() {
    int x = 18;
    printf("%d\n", ~x + 1 );
    return 0;
}
  • 運行結果如下:
-18

3、代替減法

【例題3】給定兩個int型別的正數 x x x y y y,實作 x ? y x - y x?y(注意:不能用減號),

  • 這個問題比較簡單,如果上面的相反數已經理解了,那么,x - y其實就可以表示成x + (-y),而-y又可以表示成~y + 1,所以減法 x - y就可以用x + ~y + 1來代替,
  • 代碼實作如下:
#include <stdio.h>
int main() {
    int a = 8;
    int b = 17; 
    printf("%d\n", a + ~b + 1 );
    return 0;
}
  • 運行結果為:
-9

4、代替加法

【例題4】給定兩個int型別的正數 x x x y y y,實作 x + y x + y x+y(注意:不能用加號),

  • 我們可以把x + y變成x - (-y),而-y又可以替換成 ~y + 1
  • 所以x + y就變成了x - ~y - 1,不用加號實作了加法運算,
#include <stdio.h>
int main() {
    int x = 18;
    int y = 7; 
    printf("%d\n", x - ~y - 1 );
    return 0;
}
  • 運行結果為:
25

通過這一章,我們學會了:
??1)按位取反運算子;
??2)補碼的運算;
??3)有符號整型和無符號整型;
??4)相反數、加法、減法、等于判定的另類解法;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第42題】按位取反~的應用 | 相反數

(18)- 位運算 << 的應用

一、左移運算子

1、左移的二進制形態

  • 左移運算子是一個二元的位運算子,也就是有兩個運算元,表示為x << y,其中xy均為整數,
  • x << y念作:“將 x x x 左移 y y y 位”,這里的位當然就是二進制位了,那么它表示的意思也就是:先將 x x x 用二進制表示,然后再左移 y y y 位,并且在尾部添上 y y y 個零,
  • 舉個例子:對于二進制數 2 3 10 = ( 10111 ) 2 23_{10} = (10111)_2 2310?=(10111)2? 左移 y y y 位的結果就是:
    ( 10111 0...0 ? y ) 2 (10111\underbrace{0...0}_{\rm y})_2 (10111y 0...0??)2?

2、左移的執行結果

  • x << y的執行結果等價于:
  • x × 2 y x \times 2^y x×2y
  • 如下代碼:
#include <stdio.h>
int main() {
    int x = 3;
    int y = 5;
    printf("%d\n", x << y);
    return 0;
}
  • 輸出結果為:
96
  • 正好符合這個左移運算子的實際含義:
  • 96 = 3 × 2 5 96 = 3 \times 2^5 96=3×25

最常用的就是當 x = 1 x = 1 x=1 時,1 << y代表的就是 2 y 2^y 2y,即 2 的冪,

3、負數左移的執行結果

  • 所謂負數左移,就是x << y中,當x為負數的情況,代碼如下:
#include <stdio.h>
int main() {
    printf("%d\n", -1 << 1);
    return 0;
}
  • 它的輸出如下:
-2
  • 我們發現同樣是滿足 x × 2 y x \times 2^y x×2y 的,這個可以用補碼來解釋,-1的補碼為:
  • 11111111 11111111 11111111 11111111 11111111 \ 11111111 \ 11111111 \ 11111111 11111111 11111111 11111111 11111111
  • 左移一位后,最高位的 1 就沒了,低位補上 0,得到:
  • 11111111 11111111 11111111 11111110 11111111 \ 11111111 \ 11111111 \ 11111110 11111111 11111111 11111111 11111110
  • 而這,正好是 -2的補碼,同樣,繼續左移 1 位,得到:
  • 11111111 11111111 11111111 11111100 11111111 \ 11111111 \ 11111111 \ 11111100 11111111 11111111 11111111 11111100
  • 這是-4的補碼,以此類推,所以負整數的左移結果同樣也是 x × 2 y x \times 2^y x×2y

可以理解成 - (x << y)(-x) << y是等價的,

4、左移負數位是什么情況

  • 剛才我們討論了 x < 0 x < 0 x<0 的情況,那么接下來,我們試下 y < 0 y < 0 y<0 的情況會是如何?
  • 是否同樣滿足: x × 2 y x \times 2^y x×2y 呢?
  • 如果還是滿足,那么兩個整數的左移就有可能產生小數了,
  • 看個例子:
#include <stdio.h>
int main() {
    printf("%d\n", 32 << -1);   // 16
    printf("%d\n", 32 << -2);   // 8
    printf("%d\n", 32 << -3);   // 4
    printf("%d\n", 32 << -4);   // 2
    printf("%d\n", 32 << -5);   // 1
    printf("%d\n", 32 << -6);   // 0
    printf("%d\n", 32 << -7);   // 0
    return 0;
}
  • 雖然能夠正常運行,但是結果好像不是我們期望的,而且會報警告如下:

[Warning] left shift count is negative [-Wshift-count-negative]

  • 實際上,編輯器告訴我們盡量不用左移的時候用負數,但是它的執行結果不能算錯誤,起碼例子里面對了,結果不會出現小數,而是取整了,
  • 左移負數位其實效果和右移對應正數數值位一致,右移相關的內容,我們會在 光天化日學C語言(19)- 位運算 >> 的應用 中講到,

5、左移時溢位會如何

  • 我們知道,int型別的數都是 32 位的,最高位代表符號位,那么假設最高位為 1,次高位為 0,左移以后,符號位會變成 0,會產生什么問題呢?
  • 舉個例子,對于 ? 2 31 + 1 -2^{31}+1 ?231+1 的二進制表示為:最高位和最低位為1,其余為零,
#include <stdio.h>
int main() {
    int x = 0b10000000000000000000000000000001;
    printf("%d\n", x);                          // -2147483647
    return 0;
}
  • 輸出結果為:
-2147483647
  • 那么,將它進行左移一位以后,得到的結果是什么呢?
#include <stdio.h>
int main() {
    int x = 0b10000000000000000000000000000001;
    printf("%d\n", x << 1);
    return 0;
}

  • 我們盲猜一下,最高位的 1 被移出去,最低位補上 0,結果應該是0b10
  • 實際輸出的結果,的確是:
2
  • 但是如果按照 x × 2 y x \times 2^y x×2y 答案應該是 ( ? 2 31 + 1 ) × 2 = ? 2 32 + 2 (-2^{31}+1) \times 2 = -2^{32}+2 (?231+1)×2=?232+2
  • 這里又回到了補碼的問題上,事實上,在計算機中,int整型其實是一個環,溢位以后又會回來,而環的長度正好是 2 32 2^{32} 232,所以 ? 2 32 + 2 = 2 -2^{32}+2 = 2 ?232+2=2,這個就有點像同余的概念,這兩個數是模 2 32 2^{32} 232 同余的,更多關于同余的知識,可以參考我的演算法系列文章:夜深人靜寫演算法(三)- 初等數論入門(學生黨記得找我開試讀),

二、左移運算子的應用

1、取模轉化成位運算

  • 對于 x x x 模上一個 2 的次冪的數 y y y,我們可以轉換成位與上 2 y ? 1 2^y-1 2y?1
  • 即在數學上的:
  • x m o d 2 y x \ mod \ 2^y x mod 2y
  • 在計算機中就可以用一行代碼表示:x & ((1 << y) - 1)

2、生成標記碼

??我們可以用左移運算子來實作標記碼,即1 << k作為第 k k k 個標記位的標記碼,這樣就可以通過一句話,實作對標記位置 0、置 1、取反等操作,

1)標記位置1

【例題1】對于 x x x 這個數,我們希望對它二進制位的第 k k k 位(從0開始,從低到高數)置為 1,

  • 置 1 操作,讓我們聯想到了 位或 運算,
  • 它的特點是:位或上 1,結果為 1;位或上0,結果不變,
  • 所以我們對標記碼的要求是:第 k k k 位為 1,其它位為 0,正好是(1 << k),那么將 第 k k k 位 置為 1 的陳述句可以寫成:x | (1 << k)
  • 有關位或運算的更多內容,可以參考:光天化日學C語言(15)- 位運算 | 的應用,

2)標記位置0

【例題2】對于 x x x 這個數,我們希望對它二進制位的第 k k k 位(從0開始,從低到高數)置為 0,

  • 置 0 操作,讓我們聯想到了 位與 運算,
  • 它的特點是:位與上 0,結果為 0;位與上 1,結果不變,
  • 所以在我們對標記碼的要求是:第 k k k 位為 0,其它位為 1,我們需要的是(~(1 << k)),那么將 第 k k k 位 置為 0 的陳述句可以寫成:x & (~(1 << k))
  • 有關位與運算的更多內容,可以參考:光天化日學C語言(14)- 位運算 & 的應用,
  • 有關 按位取反 運算的更多內容,可以參考:光天化日學C語言(17)- 位運算 ~ 的應用,

3)標記位取反

【例題3】對于 x x x 這個數,我們希望對它二進制位的第 k k k 位(從0開始,從低到高數)取反,

  • 取反操作,聯想到的是 異或 運算,
  • 它的特點是:異或上 1,結果取反;異或上 0,結果不變,
  • 所以我們對標記碼的要求是:第 k k k 位為1,其余位為 0,其值為(1 << k),那么將 第 k k k 位 取反的陳述句可以寫成:x ^ (1 << k)
  • 有關 異或 運算的更多內容,可以參考:光天化日學C語言(16)- 位運算 ^ 的應用,

3、生成掩碼

  • 同樣,我們可以用左移來生成一個掩碼,完成對某個數的二進制末 k k k 位執行一些操作,
  • 對于(1 << k)的二進制表示為:1 加上 k 個 0,那么 (1 << k) - 1的二進制則代表 k k k 個 1,
  • 把末尾的 k k k 位都變成 1,可以寫成:x | ((1 << k) - 1)
  • 把末尾的 k k k 為都變成 0,可以寫成:x & ~((1 << k) - 1)
  • 把末尾的 k k k 位都取反,可以寫成:x ^ ((1 << k) - 1)

通過這一章,我們學會了:
??1)位運算 << 的用法;
??2)用 << 來生成標記位;
??3)用 << 來生成掩碼;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第43題】左移的應用 | 一句話判斷一個數是否是 2 的冪

(19)- 位運算 >> 的應用

一、右移運算子

1、右移的二進制形態

  • 右移運算子是一個二元的位運算子,也就是有兩個運算元,表示為x >> y,其中xy均為整數,
  • x >> y念作:“將 x x x 右移 y y y 位”,這里的位當然就是二進制位了,那么它表示的意思也就是:先將 x x x 用二進制表示,對于正數,右移 y y y 位;對于負數,右移 y y y 位后高位都補上 1,
  • 舉個例子:對于二進制數 8 7 10 = ( 1010111 ) 2 87_{10} = (1010111)_2 8710?=(1010111)2? 左移 y y y 位的結果就是:
    ( 1010 ) 2 (1010)_2 (1010)2?

2、右移的執行結果

  • x >> y的執行結果等價于:
  • ? x 2 y ? \lfloor \frac x {2^y} \rfloor ?2yx??
  • 其中 ? a ? \lfloor a\rfloor ?a? 代表對 a a a 取下整,
  • 如下代碼:
#include <stdio.h>
int main() {
    int x = 0b1010111;
    int y = 3;
    printf("%d\n", x >> y);
    return 0;
}
  • 輸出結果為:
10
  • 正好符合這個右移運算子的實際含義:
  • 10 = ? 87 2 3 ? 10 = \lfloor \frac {87} {2^3} \rfloor 10=?2387??

由于除法可能造成不能整除,所以才會有 取下整 這一步運算,

3、負數右移的執行結果

  • 所謂負數右移,就是x >> y中,當x為負數的情況,代碼如下:
#include <stdio.h>
int main() {
    printf("%d\n", -1 >> 1);
    return 0;
}
  • 它的輸出如下:
-1
  • 我們發現同樣是滿足 ? x 2 y ? \lfloor \frac x {2^y} \rfloor ?2yx?? 的(注意,負數的 取下整 和 正數 是正好相反的),這個可以用補碼來解釋,-1的補碼為:
  • 11111111 11111111 11111111 11111111 11111111 \ 11111111 \ 11111111 \ 11111111 11111111 11111111 11111111 11111111
  • 右移一位后,由于是負數,高位補上 1,得到:
  • 11111111 11111111 11111111 11111111 11111111 \ 11111111 \ 11111111 \ 11111111 11111111 11111111 11111111 11111111
  • 而這,正好是 -1的補碼,同樣,繼續右移 1 位,得到:

可以理解成 - (x >> y)(-x) >> y是等價的,

【例題1】要求不運行代碼,肉眼看出這段代碼輸出多少,

#include <stdio.h>
int main() {
    int x = (1 << 31) | (1 << 30) | 1;
    int y = (1 << 31) | (1 << 30) | (1 << 29);
    printf("%d\n", (x >> 1) / y);
    return 0;
}

4、右移負數位是什么情況

  • 剛才我們討論了 x < 0 x < 0 x<0 的情況,那么接下來,我們試下 y < 0 y < 0 y<0 的情況會是如何?
  • 是否同樣滿足: ? x 2 y ? \lfloor \frac x {2^y} \rfloor ?2yx?? 呢?
  • 如果還是滿足,那么兩個整數的左移就有可能產生小數了,
  • 看個例子:
#include <stdio.h>
int main() {
    printf("%d\n", 1 >> -1);   // 2
    printf("%d\n", 1 >> -2);   // 4
    printf("%d\n", 1 >> -3);   // 8
    printf("%d\n", 1 >> -4);   // 16
    printf("%d\n", 1 >> -5);   // 32
    printf("%d\n", 1 >> -6);   // 64
    printf("%d\n", 1 >> -7);   // 128
    return 0;
}
  • 雖然能夠正常運行,但是結果好像不是我們期望的,而且會報警告如下:

[Warning] right shift count is negative [-Wshift-count-negative]

  • 實際上,編輯器告訴我們盡量不用右移的時候用負數,但是它的執行結果不能算錯誤,起碼例子里面對了,
  • 右移負數位其實效果和左移對應正數數值位一致,

二、右移運算子的應用

1、去掉低 k 位

【例題2】給定一個數 x x x,去掉它的低 k k k 位以后進行輸出,

  • 這個問題,可以直接通過右移來完成,如下:x >> k

2、取低位連續 1

【例題3】獲取一個數 x x x 低位連續的 1 并且輸出,

  • 對于一個數 x x x,假設低位有連續 k k k 個 1,如下:
  • ( . . . 0 1...1 ? k ) 2 (...0\underbrace{1...1}_{\rm k})_2 (...0k 1...1??)2?
  • 然后我們將它加上 1 以后,得到的就是:
  • ( . . . 1 0...0 ? k ) 2 (...1\underbrace{0...0}_{\rm k})_2 (...1k 0...0??)2?
  • 這時候將這兩個數異或結果為:
  • ( 1...1 ? k + 1 ) 2 (\underbrace{1...1}_{\rm {k+1}})_2 (k+1 1...1??)2?
  • 這時候,再進行右移一位,就得到了 連續 k k k 個 1 的值,也正是我們所求,
  • 所以可以用以下陳述句來求:(x ^ (x + 1)) >> 1

3、取第k位的值

【例題4】獲取一個數 x x x 的第 k ( 0 ≤ k ≤ 30 ) k(0 \le k \le 30) k(0k30) 位的值并且輸出,

  • 對于二進制數來說,第 k k k 位的值一定是 0 或者 1,
  • 而 對于 1 到 k ? 1 k-1 k?1 位的數字,對于我們來說是沒有意義的,我們可以用右移來去掉,再用位與運算子來獲取二進制的最后一位是 0 還是 1,如下:(x >> k) & 1

通過這一章,我們學會了:
??1)位運算 >> 的用法;
??2)用 >> 來取低位連續 1;
??3)用 >> 取第 k k k 位的值;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第44題】右移的應用 | (更新中)

(20)- 賦值運算子

一、賦值運算子概覽

1、賦值運算子

  • 今天我們來講一下賦值運算子,
  • 對于賦值運算子,主要分為兩類:簡單賦值運算子 和 復合賦值運算子,如下圖所示:
  • 簡單賦值運算子,我們之前在講 光天化日學C語言(03)- 變數 的時候就已經遇到了,它的表示形式如下: 變 量 = 常 量 變 量 = 表 達 式 \begin{aligned}變數 &= 常量 \\ 變數 &= 運算式\end{aligned} ?==?
  • 即將賦值符號=右邊的運算元的值賦值給左邊的運算元,

2、賦值運算式

  • 類似這樣的運算式,我們稱之為 賦值運算式
  • 例如:
a = 10189;
a = a + 5;
  • 任何運算式都是有值的,賦值運算式也不例外,它的值就是=右邊的值,
  • 試想一下這段代碼的輸出是多少?
#include <stdio.h>
int main() {
    int a = 5;
    int b = (a = 5); 
    printf("%d\n", b);
    return 0;
} 

  • 運行結果為:
5
  • 原因就是因為運算式a = 5的值為5,從而等價于b = 5

3、賦值運算的自動型別轉換

  • 賦值運算子會進行自動型別轉換,轉換型別就是左邊運算元的型別,
#include <stdio.h>
int main() {
    int a = 0;
    a = a + 1.5;
    printf("%d\n", a);
    return 0;
} 
  • 輸出的結果為:
1
  • 有關型別轉換的內容,可以參考 光天化日學C語言(12)- 型別轉換,

4、連續賦值

  • 我們來看一個例子,如下:
#include <stdio.h>
int main() {
    int a, b, c, d = 0;
    a = b = c = d = d == 0;
    printf("%d\n", a);
    return 0;
} 
  • 這段代碼的運行結果為:
1
  • 為什么呢?
  • 它其實等價于:
#include <stdio.h>
int main() {
    int a, b, c, d = 0;
    a = ( b = (c = ( d = (d == 0) ) ) );
    printf("%d\n", a);
    return 0;
} 
  • 這里涉及到兩個概念:運算子優先級、運算子結合性,
  • 具體的內容,我們會在后續內容中詳細講解,現在你只需要知道 賦值運算子=的優先級低于關系運算子==,所以d = d == 0等價于d = (d == 0);而賦值運算子=的結合性是從右到左,所以a = b = c等價于a = (b = c)

二、復合賦值運算子

  • 首先來看一個賦值陳述句,如下:
    int love;
    love = love + 1314;
  • 像這種運算式左邊的變數重復出現在運算式的右邊,則可以縮寫成:
    int love;
    love += 1314;
  • 而這里的+=就是復合賦值運算子,類似的復合賦值運算子還有很多,總共分為兩大類:算術賦值運算子、位賦值運算子,

1、算術賦值運算子

  • 算術運算子我們之前已經了解過了,具體可以參考這篇文章:光天化日學C語言(09)- 算術運算子,
  • 而算術賦值運算子就是先進行算術運算,再進行賦值,算術賦值運算子的表格如下:
運算子簡稱描述舉例
+=加且賦值運算子右邊運算元 加上 左邊運算元 的結果賦值給 左邊運算元a += b等價于a = a + b
-=減且賦值運算子左邊運算元 減去 右邊運算元 的結果賦值給 左邊運算元a -= b等價于a = a - b
*=乘且賦值運算子右邊運算元 乘以 左邊運算元 的結果賦值給 左邊運算元a *= b等價于a = a * b
/=除且賦值運算子左邊運算元 除以 右邊運算元 的結果賦值給 左邊運算元a /= b等價于a = a / b
%=求模且賦值運算子兩個運算元的模,并將結果賦值給 左邊運算元a %= b等價于a = a % b

2、位賦值運算子

  • 位運算子我們之前已經了解過了,具體可以參考這篇文章:光天化日學C語言(13)- 位運算概覽,
  • 而位賦值運算子就是先進行位運算,再進行賦值,位賦值運算子的表格如下:
運算子簡稱描述舉例
&=按位與且賦值運算子左邊運算元 按位與上 右邊運算元 的結果賦值給 左邊運算元a &= b等同于a = a & b
|=按位或且賦值運算子左邊運算元 按位或上 右邊運算元 的結果賦值給 左邊運算元a |= b等同于a = a | b
^=按位異或且賦值運算子左邊運算元 按位異或上 右邊運算元 的結果賦值給 左邊運算元a ^= b等同于a = a ^ b
<<=左移且賦值運算子左邊運算元 左移 右邊運算元 的位數后的結果賦值給 左邊運算元a <<= b等同于a = a << b
>>=右移且賦值運算子左邊運算元 右移 右邊運算元 的位數后的結果賦值給 左邊運算元a >>= b等同于a = a >> b

三、復合賦值運算式

  • 對于兩個運算式 e 1 e_1 e1? e 2 e_2 e2?,有復合賦值運算式:
  • e 1 o p = e 2 e_1 \ _{op=} \ e_2 e1? op=? e2?
  • 等價于:
  • e 1 = ( e 1 ) o p ( e 2 ) e_1 = (e_1) \ _{op} \ (e_2) e1?=(e1?) op? (e2?)
  • 其中 o p op op 就是上文提到的那 10 個 復合賦值運算子,

這樣寫的好處有三個:
??1)前一種形式, e 1 e_1 e1? 只計算一次;第二種形式要計算兩次,
??2)前一種形式,不需要加上圓括號;第二種形式的圓括號不可少,
??3)看起來簡潔清晰;

  • 舉個極端的例子:
  • a.b.c.d.e.f[ 1024 + g.h.i.j.k.l ] = a.b.c.d.e.f[ 1024 + g.h.i.j.k.l ] + 5
    炸裂的🤣🤣🤣!!!
  • 利用復合賦值運算式,我們就可以寫成:a.b.c.d.e.f[ 1024 + g.h.i.j.k.l ] += 5(當然,這個例子比較極端,實際編碼中千萬不要寫出這樣的代碼哦),

通過這一章,我們學會了:
??1)賦值運算子;
??2)賦值運算式;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第15題】給定一個整數,對它進行逆序輸出 | 完美的堆疊思想

(21)- 逗號運算子

一、逗號運算子

  • 今天,我們就來看下逗號運算子和逗號運算式吧,
  • 在 C語言 中,可以把多個運算式用逗號連接起來,構成一個更大的運算式,其中的逗號稱為 逗號運算子,所構成的運算式稱為 逗號運算式,逗號運算式中用逗號分開的運算式分別求值,以最后一個運算式的值作為整個運算式的值,

簡單來說,逗號運算式遵循兩點原則:
??1)以逗號分隔的運算式單獨計算;
??2)逗號運算式的值為最后一個運算式的值;

二、逗號運算子的應用

1、連續變數定義

  • 逗號運算通常用于變數的連續定義,如下:
#include <stdio.h>
int main() {
    int a = 1, b = 2, c = 3, d = 1 << 6, e;
    printf("%d\n", a + b + c + d);
    return 0;
}
  • 這里的int a = 1, b = 2, c = 3, d = 1 << 6, e就是逗號運算式,

2、回圈陳述句賦初值

  • 逗號運算通常用于for結構的括號內的第一個運算式,用于給多個區域變數賦值,
  • 一段對 110的數求立方和的代碼,如下:
#include <stdio.h>
int main() {
    int i, s;
    for(i = 1, s = 0; i <= 10; ++i) {
        s += i*i*i;
    }
    printf("%d\n", s);
    return 0;
}
  • 這里的i = 1, s = 0就是逗號運算式,
  • 有關于for的內容,會在后面的章節來介紹,暫時只需要知道可以使用逗號運算式來對一些變數賦予初值,

3、交換變數

  • 我們在實作交換變數的時候,往往需要三句話:
int tmp;
tmp = a;
a = b;
b = tmp;
  • 有了逗號運算式,我們就可以這么寫:
int tmp;
tmp = a, a = b, b = tmp;

三、逗號運算子注意事項

  • 需要注意的是,逗號運算子的優先級非常低,甚至比賦值運算子還要低,所以當它和賦值運算子相遇時,是優先計算賦值運算的,如下代碼所示:
#include <stdio.h>
int main() {
    int x, y, a, b;
    a = (1, x = 2, y = 3);
    b = 1, x = 9, y = 3; 
    printf("%d %d\n", a, b);
    return 0;
}
  • 這段代碼中ab的的賦值,只差了一個括號,但是結果截然不同,
  • 輸出的結果為:
3 1
  • 原因是因為(1, x = 2, y = 3)運算式的值為以逗號分隔的最后一個運算式的值,即3;而在b = 1, x = 9, y = 3中,由于逗號運算子的優先級很低,導致運算式分成了三部分:b = 1x = 9y = 3,所以才有 a = 3 a=3 a=3 b = 1 b=1 b=1

通過這一章,我們學會了:
??1)逗號運算子;
??2)逗號運算式;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第32題】給定一個字串,原地對它進行翻轉并輸出

(22)- 運算子優先級和結合性

一、運算子簡介

  • 運算子用于執行程式代碼運算,會針對一個、兩個或多個運算元來進行運算,例如:1 + 2,其運算元是 1 和 2,而運算子則是 “+”(加號),
  • C語言把除了 控制陳述句輸入輸出 以外的幾乎所有的基本操作都作為運算子處理,可見一斑,

二、運算子分類

  • 將按功能分類,可以分為:后綴運算子、單目運算子、算術運算子、關系運算子、位運算子、邏輯運算子、條件運算子、賦值運算子、逗號運算子,
  • 在之前的章節也有介紹了很多運算子,這里簡單做個總結:
運算子型別運算子舉例參考文章
后綴運算子[]下標運算會在陣列章節講解,待更新
單目運算子(type)強制轉換光天化日學C語言(12)- 型別轉換
算術運算子+加號光天化日學C語言(09)- 算術運算子
移位運算子<<左移光天化日學C語言(18)- 位運算 << 的應用
關系運算子<小于光天化日學C語言(10)- 關系運算子
雙目位運算子&位與光天化日學C語言(14)- 位運算 & 的應用
雙目邏輯運算子&&光天化日學C語言(11)- 邏輯運算子
條件運算子? :會在if陳述句章節講解,待更新
賦值運算子<<=左移后賦值光天化日學C語言(20)- 賦值運算子與賦值運算式
逗號運算子,逗號光天化日學C語言(21)- 逗號運算子

三、運算子的優先級和結合性

1、運算子優先級表

優先級運算子名稱形式舉例
1[]陣列下標陣列名[常量運算式]a[2]
1()圓括號(運算式) 或 函式名(形參表)(a+1)
1.物件的成員選擇物件.成員名a.b
1->指標的成員選擇指標.成員名a->b
2+正號+運算式+5
2-負號-運算式-5
2(type)強制型別轉換(資料型別)運算式(int)a
2++自增運算子++變數名 / 變數名++++i
2--自增運算子–變數名 / 變數名–--i
2!邏輯非!運算式!a[0]
2~按位取反~運算式~a
2&取地址&變數名&a
2*解參考*指標變數名*a
2sizeof取長度sizeof(運算式)sizeof(a)
3*運算式 * 運算式3 * 5
3/運算式 / 運算式3 / 5
3%整型運算式 % 整型非零運算式3 % 5
4+運算式 + 運算式a + b
4-運算式 - 運算式a - b
5<<左移變數<<運算式1<<5
5>>右移變數>>運算式x>>1
6<小于運算式<運算式1 < 2
6<=小于等于運算式<=運算式1 <= 2
6>大于運算式>運算式1 > 2
6>=大于等于運算式>=運算式1 >= 2
7==等于運算式==運算式1 == 2
7!=不等于運算式!=運算式1 != 2
8&等于運算式&運算式1 & 2
9^等于運算式^運算式1 ^ 2
10|等于運算式\運算式1 | 2
11&&邏輯與運算式&&運算式a && b
12||邏輯與運算式||運算式a || b
13?:條件運算子運算式1? 運算式2: 運算式3a>b?a:b
14=賦值變數=運算式a = b
14+=加后賦值變數+=運算式a += b
14-=減后賦值變數-=運算式a -= b
14*=乘后賦值變數*=運算式a *= b
14/=除后賦值變數/=運算式a /= b
14%=模后賦值變數%=運算式a %= b
14>>=右移后賦值變數>>=運算式a >>= b
14<<=左移后賦值變數<<=運算式a <<= b
14&=位與后賦值變數&=運算式a &= b
14^=異或后賦值變數^=運算式a ^= b
14|=位或后賦值變數|=運算式a |= b
15,逗號運算子運算式1,運算式2,…a+b,a-b

2、結合性

結合方向只有 3 個是 從右往左,其余都是 從左往右(比較符合人的直觀感受),
??(1)一個是單目運算子;
??(2)一個是雙目運算子中的 賦值運算子;
??(3)一個條件運算子,也就是C語言中唯一的三目運算子,

3、優先級

后綴運算子和單目運算子優先級一般最高,逗號運算子的優先級最低,快速記憶如下:

單目邏輯運算子 > 算術運算子 > 關系運算子 > 雙目邏輯運算子 > 賦值運算子

四、運算子的優先級和結合性舉例


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

【運行結果】65536
【結果答疑】a <<= b <<= c的計算方式等價于a = (a << (b << c)),結果為1 << 16


🧡例題2🧡
#include <stdio.h>
int main() {
    int a = 1, b = 2;
    printf("%d\n", a > b ? a + b : a - b ); 
    return 0;
}

【運行結果】-1
【結果答疑】條件運算子的優先級較低,低于關系運算子和算術運算子,所以a > b ? a + b : a - b等價于1 > 2 ? 3 : -1


🧡例題3🧡
#include <stdio.h>
int main() {
    int a = 1;
    --a && --a;
    printf("%d\n", a); 
    return 0;
}

【運行結果】0
【結果答疑】這個例子是展示邏輯與運算子&&從左往右計算程序中,一旦遇到 0 就不再進行運算了,所以--a實際上只執行了一次,


🧡例題4🧡
#include <stdio.h>
int main() { 
    int x = 0b010000; 
    printf("%d\n", x | x - 1 ); 
    return 0;
}

【運行結果】31
【結果答疑】這個例子是是將低位連續的零變成一,但是一般這樣的寫法會報警告,因為編譯程式并不知道你的訴求,到底是想先計算 | 還是先計算 -,由于這個問題我們實際要計算的是x | (x - 1),并且減法運算子-優先級高于位或運算子 | ,所以括號是可以省略的,


🧡例題5🧡
#include <stdio.h>
int main() {
    int a = 0b1010;
    int b = 0b0101;
    int c = 0b1001;
    printf("%d\n", a | b ^ c );
    return 0;
}

【運行結果】14
【結果答疑】這個例子表明了異或運算子^高于位或運算符 | ,


🧡例題6🧡
#include <stdio.h>
int main() {
    int a = 0b1010;
    int b = 0b0110;
    printf("%d\n", a & b == 2);
    return 0;
}

【運行結果】0
【結果答疑】延續【例題59】繼續看,之前a & b輸出的是2,那為什么加上等于==判定后,輸出結果反而變成0了呢?原因是因為==的優先級高于位與&,所以相當于進行了a & 0的操作,結果自然就是0了,


通過這一章,我們學會了:
??1)運算子的優先級;
??2)運算子的結合性;

  • 希望對你有幫助哦 ~ 祝大家早日成為 C 語言大神!

課后習題

  • 【第13題】給定三個數 a,b,c,從小到大輸出這三個數

第三章
資料型別的存盤方式

(23)- 整數的存盤

一、整數簡介

1、符號位 和 數值位

  • 我們知道 整數 分為 有符號整型 和 無符號整型,
  • 有符號整型,程式需要區分 符號位數值位
  • 對我們人類來說,很容易分辨;而對計算機而言,就要設計專門的電路,這就增加了硬體的復雜性,從而增加了計算的時間,

??所以,如果能夠將 符號位數值位 聯合起來,讓它們共同參與運算,不再加以區分,這樣硬體電路就會變得更加簡單,

2、整型的加減運算

  • 其次,加法減法 的引入,也將問題變得復雜,而由于減去一個數相當于加上這個數的相反數,例如:1 - 2等價于 1 + (-2)1 - (-2)等價于1 + 2

??所以,它們可以合并為一種運算,即只保留加法運算,

  • 相反數是指 數值位 相同,符號位 不同的兩個數,例如,1 和 -1 就是一對相反數,

  • 所以,我們需要做的就是設計一種簡單的、不用區分符號位和數值位的加法電路,就能同時實作加法和減法運算,首先讓我們看幾個計算機中的概念,

二、機器數和真值

1、機器數

  • 我們知道計算機是內部由 0 和 1 組成的編碼,無論是整數還是浮點數,都會涉及到負數,對于機器來說是不知道正負的,而 “正” 和 “負” 正好是兩種對立的狀態,所以規定用 “0” 表示 “正”,“1” 表示 “負”,這樣符號就被數字化了,并且將它放在有效數字的前面,就成了有符號數;
  • 把符號 “數字化” 的數稱為 機器數;

2、真值

  • 而帶有 “+” 或者 “-” 的數稱為 真值;
  • 然而,當符號位和數值部分放在一起后,如何讓它一起參與運算呢?那就要涉及到接下來要講的計算機的各種編碼了,

三、計算機編碼

1、原碼

1)定義

  • 這里的原碼并不是原始碼(源代碼)的意思,而是機器數中最簡單的一種表示形式;為了快速理解,這里只介紹 32位整數;

【定義】 符號位0 代表 正數符號位1 代表 負數數值位真值的絕對值

2)舉例

  • 1)對于十進制數 37,它的 真值 和 原碼 關系如下:
真值:+ 00000000 00000000 00000000 00100101
原碼:  00000000 00000000 00000000 00100101
  • 2)對于十進制數 -37,它的 真值 和 原碼 的關系如下:
真值:- 00000000 00000000 00000000 00100101
原碼:  10000000 00000000 00000000 00100101
  • 我們發現,對于負數的情況,原碼 加上 真值(注意,這里真值為負數)后,二進制數正好等于 1 ( 0...0 ? 31 ) 2 1(\underbrace{0...0}_{31})_2 1(31 0...0??)2?, 即 2 31 2^{31} 231,表示成公式如下: [ x ] 原 + x = 2 31 [x]_原 + x = 2^{31} [x]?+x=231

3)公式

  • 因此,我們可以通過移項,得出原碼的十進制計算公式如下:

[ x ] 原 = { x ( 0 ≤ x < 2 n ? 1 ) 2 n ? 1 ? x ( ? 2 n ? 1 < x ≤ 0 ) [x]_原 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n-1} - x & (-2^{n-1} < x \le 0) \end{cases} [x]?={x2n?1?x?(0x<2n?1)(?2n?1<x0)? ??這里 x x x 代表真值,而 n n n 的取值是 8 、 16 、 32 、 64 8、16、32、64 8163264,我們通常說的整型int都是 32位 的,本文就以 n = 32 n = 32 n=32 的情況進行闡述;

  • 原碼是最貼近人類的編碼方式,并且很容易和真值進行轉換,但是讓計算機用原碼進行加減運算過于繁瑣,如果兩個數符號位不同,需要先判斷絕對值大小,然后用絕對值大的減去絕對值小的,并且符號以絕對值大的數為準,本來是加法卻需要用減法來實作,

2、反碼

1)定義

【定義】 正數反碼 就是它的 原碼負數反碼原碼 的每一位的 0變11變0(即位運算中的按位取反);

2)舉例

  • 1)對于十進制數 37,它的 真值 和 反碼 關系如下:
真值:+ 00000000 00000000 00000000 00100101
反碼:  00000000 00000000 00000000 00100101
  • 2)對于十進制數 -37,它的 真值 和 反碼 的關系如下:
真值:- 00000000 00000000 00000000 00100101
反碼:  11111111 11111111 11111111 11011010
  • 我們發現,對于負數的情況,反碼 減去 真值(注意,這里真值為負數)后,負負得正,轉換成二進制位相加正好等于 ( 1...1 ? 32 ) 2 (\underbrace{1...1}_{32})_2 (32 1...1??)2?, 即 2 32 ? 1 2^{32}-1 232?1,表示成公式如下: [ x ] 反 ? x = 2 32 ? 1 [x]_反 - x = 2^{32}-1 [x]??x=232?1

3)公式

  • 因此,通過移項,我們可以得出反碼的十進制計算公式如下:

[ x ] 反 = { x ( 0 ≤ x < 2 n ? 1 ) 2 n ? 1 + x ( ? 2 n ? 1 < x ≤ 0 ) [x]_反 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n}-1 + x & (-2^{n-1} < x \le 0) \end{cases} [x]?={x2n?1+x?(0x<2n?1)(?2n?1<x0)? ??這里 x x x 代表真值,而 n n n 的取值是 8 、 16 、 32 、 64 8、16、32、64 8163264,我們通常說的整型int都是 32位 的,本文就以 n = 32 n = 32 n=32 的情況進行闡述;

  • 反碼有個很難受的點,就是 ( 0 0...0 ? 31 ) 2 (0\underbrace{0...0}_{31})_2 (031 0...0??)2? ( 1 0...0 ? 31 ) 2 (1\underbrace{0...0}_{31})_2 (131 0...0??)2? 都代表零,就是我們常說的 正零 和 負零,正如公式中看到的,當真值為 0 的時候,有兩種情況,這就產生了二義性,而且浪費了一個整數表示形式,

3、補碼

1)定義

【定義】 正數補碼 就是它的 原碼負數補碼 為 它的反碼加一

2)舉例

  • 1)對于十進制數 37,它的 真值 和 補碼 關系如下:
真值:+ 00000000 00000000 00000000 00100101
補碼:  00000000 00000000 00000000 00100101
  • 2)對于十進制數 -37,它的 真值 和 反碼 的關系如下:
真值:- 00000000 00000000 00000000 00100101
補碼:  11111111 11111111 11111111 11011011
標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more