目錄
補碼
字元類型 VS 整型
字元串
算數運算符
類型轉換
分支結構
關系運算符
邏輯運算符
if語句
switch-case語句
if和switch語句嵌套
循環結構
while循環
do...while循環
for循環
continue
多學一點
for VS while
goto
條件運算符
彩蛋
第一天忘了跟大家說C環境的配置,點選這裡檢視C語言初學者如何配置開發環境。
補碼
計算機是用補碼的形式存儲整型資料的。正數的原返補都是一樣的,負數的補碼需要三步才能得到:
- 先把該數的絕對值轉換成二進制形式,也就是該數的源碼 ------ 源碼就是原來的樣子
- 再把數值位按位取反,得到反碼 ------ 反碼就是反着幹,0變1,1變0;
- 最後将第2步的值加一就得到了補碼 ------ 補碼就是補1。
偷學C語言第二天
為什麼會引入補碼?詳情見:計算機中數的表示 & 使用補碼的好處
字元類型 VS 整型
先看個例子:
#include<stdio.h>
int main()
{
char c = 'A';
printf("%c = %d\n",c,c);
}
運作結果:
字元'A'為什麼以整型輸出時是65?還記得第一天在講轉義字元時提到的ASCII表嗎,對的,這裡就是通過查ASCII表查到字元'A'對應的十進制數是“65”,是以字元類型是特殊的整型。
再來看個例子:
#include<stdio.h>
int main()
{
char height = 175;
printf("我的海拔是%dcm\n",height);
}
哎呀我去,不得了了,咋還長到地底下去了,我又不是胡蘿蔔(´•༝•`) 這又是咋回事呢?還記得第一天學C時提到char的取值範圍是-128到127或0到255,為什麼還有個或呢?這是因為C标準中沒有規定char預設是signed還是unsigned,而是由編譯系統決定它的預設限定符,signed char的取值範圍是 -128 ~ 127;unsigned char的取值範圍是 0 ~ 255。是以我們應該手動指定char的限定符。
字元串
C沒有專門為存儲字元串設計一個單獨的類型,因為沒必要。字元串事實上就是一串字元。是以隻需要在記憶體中開辟一塊連續空間,然後存放一串字元類型的變量即可。
- 聲明字元串的文法:char 變量名[字元的數量];
- 指派:事實上就是對這一塊連續空間裡邊的每一個字元變量進行指派。我們通過索引号來獲得每個字元變量的空間,文法格式:變量名[索引号] = 字元;
#include<stdio.h> int main() { char words[12]; words[0] = 'I'; words[1] = ' '; words[2] = 'L'; words[3] = 'o'; words[4] = 'v'; words[5] = 'e'; words[6] = ' '; words[7] = 'C'; words[8] = 'h'; words[9] = 'i'; words[10] = 'n'; words[11] = 'a'; /*'\0'是字元串結束的标記,不可以省掉*/ words[12] = '\0'; /*聲明和指派放在一塊,稱為初始化,[]中的數量可以省掉,編譯期會自動幫我們計算*/ char words2[] = {'I',' ','L','o','v','e',' ','C','h','i','n','a','\0'}; /*字元串常量可以用雙引号直接括起來,不必在末尾追加'\0',系統會自動追加*/ char words3[] = {"I Love China"}; /*字元串常量可以把大括号也省掉*/ char words4[] = "I Love China"; printf("%s\n",words); printf("%s\n",words2); printf("%s\n",words3); printf("%s\n",words4); return 0; }
算數運算符
假設A的值是10,B的值是20
運算符 | 描述 | 執行個體 |
---|---|---|
+ | 把兩個操作數相加(雙目) | A + B 将得到 30 |
- | 從第一個操作數中減去第二個操作數(雙目) | A - B 将得到 -10 |
* | 把兩個操作數相乘(雙目) | A * B 将得到 200 |
/ | 分子除以分母(雙目) | B / A 将得到 2 |
% | 取模運算符,整除後的餘數(雙目) | B % A 将得到 0 |
++ | 自增運算符,整數值增加 1(單目) | A++ 将得到 11 |
-- | 自減運算符,整數值減少 1(單目) | A-- 将得到 9 |
代碼示例:
#include<stdio.h>
int main() {
int a = 10,b = 20;
printf("a + b = %d\n",a + b);
printf("a - b = %d\n",a - b);
printf("a * b = %d\n",a * b);
printf("b / a= %d\n",b / a);
/*
對于整型類型資料的除法運算,是采取直接舍棄小數部分的方式,而不是什麼四舍五入。
*/
printf("a / b= %d\n",a / b);
printf("b %% a = %d\n",b % a);
/*
++在前,先自增再指派;++在後,先指派再自增;自減同理。
*/
printf("a++ = %d\n",a++);//a++的值是10
printf("++a = %d\n",++a);//經過上一步運算a的值就變成11了,++a在11的基礎上先自增1變成12,再指派,是以此時a的值是12
printf("a-- = %d\n",a--);
printf("--a = %d\n",--a);
return 0;
}
需要注意的幾點問題:
- 因為鍵盤上沒有乘号和除号兩個按鍵,是以用星号(*)和斜杠(/)代替,幾乎所有程式設計語言都是如此。
- 對于整數間的除法是采取直接舍棄小數部分的方式,而不是什麼四舍五入。
- 對于浮點數間的除法則能獲得一個相對逼近結果的值(如果除不盡或位數特别多的話)。
- 百分号(%)取模(求餘)運算符要求兩邊的操作數必須都是整數,其結果也是整數。
- 雙目單目指的是運算符有幾個操作數。
類型轉換
#include<stdio.h>
#include<math.h>
int main() {
/*
自動轉換:兩種不同類型的資料做運算,容量小的類型會自動向上轉型成容量大的類型再做運算,是以3+3.0得到的是浮點型,以整型格式輸出,結果肯定不對
*/
printf("整型輸出:%d\n",3+3.0);
printf("浮點輸出:%f\n",3.0+3.0);
/*
強制轉換:文法格式:(目标類型)原資料
強制類型轉換會損失精度
*/
printf("強制類型轉換:%d\n",3+(int)3.6);//浮點型的3.6強轉成int後變成3了
}
分支結構
之前我們寫的代碼都是順序結構,即按代碼編寫順序依次執行。但我們生活中會存在一種“如果怎樣就怎樣否則會怎樣”的情況,是以就有了分支結構,在學習分支結構之前,我們得先了解關系運算符和邏輯運算符。
關系運算符
關系運算符用于比較兩個數或者兩個表達式的大小關系,包含>(大于)、<(小于)、=(等于)、>=(大于或等于)、<=(小于或等于)、==(等于)、!=(不等于),關系運算符的優先級低于算數運算符,結合性都是從左到右。關系運算符是雙目運算符,用關系運算符将兩邊的變量、資料或表達式連接配接起來,稱之為關系表達式。關系表達式的結果是一個邏輯值,或真(1)或假(0)。
#include<stdio.h>
int main() {
int a = 6,b = 8;
printf("a > b結果是%d\n",a > b);
printf("a < b結果是%d\n",a < b);
printf("a >= b結果是%d\n",a >= b);
printf("a <= b結果是%d\n",a <= b);
printf("a != b結果是%d\n",a != b);
}
邏輯運算符
邏輯運算符包含&&(與)、||(或)、!(非),優先級:!>&&>||。邏輯運算符存在短路求值,詳情參見下面代碼示例:
#include<stdio.h>
int main() {
int a = 6,b = 8;
/*
我們輸入非0數,C語言就會判斷為真,否則為假;
C語言回報給我們的值隻有0和1,0表示假,1表示真。
*/
/*&&:兩邊同為真結果才為真,否則皆為假。*/
printf("a && b = %d\n",a && b);
/*||:兩邊同為假結果才為假,否則皆為真。*/
printf("(a = 0) || b = %d\n",(a = 0) || b);
/*!:單目運算符,原資料為真,結果則為假,否則為真,即取反。*/
printf("!0 || a < b = %d\n",!0 || a < b);
/*
短路求值:隻有當第一個操作數或表達式結果不能确定時,才會對第二個資料進行求值操作
*/
int c = (a = 0) && (b = 2);
printf("a = %d,b = %d\n",a,b);
int d = (a = 1) || (b = 6);
printf("a = %d,b = %d\n",a,b);
return 0;
}
if語句
if語句有三種形态:
- if(邏輯資料){//業務代碼} ;
#include<stdio.h> int main() { int age; printf("閣下貴庚:"); scanf("%d",&age); if(age >=18) { printf("歡迎光臨,祝您玩的開心!"); } return 0; }
- if(邏輯資料){//業務代碼}else{//業務代碼};
#include<stdio.h> int main() { int age; printf("閣下貴庚:"); scanf("%d",&age); if(age >=18) { printf("歡迎光臨,祝您玩的開心!"); } else { printf("未成年人禁止入内,謝謝配合!"); } return 0; }
- if(邏輯資料){//業務代碼}else if(邏輯值){//業務代碼}else if(邏輯值){//業務代碼}...else{//業務代碼}。
#include<stdio.h> int main() { float score; char grade; printf("請輸入學生成績:"); scanf("%f",&score); /*輸入的是浮點型,以整型列印,結果為0*/ printf("使用者輸入的成績是(以整型格式輸出):%d\n",score); if(score >=90) { grade = 'A'; } else if(score >= 80 && score < 90) { grade = 'B'; } else if(score >= 70 && score < 80) { grade = 'C'; } else if(score >= 60 && score < 70) { grade = 'D'; } else if(score >= 0 && score < 60) { grade = 'E'; } else { printf("學生分數不能給負數,太打擊學生自信心了!"); return 0; } printf("該學生分數等級是%c\n",grade); return 0; }
switch-case語句
可以認為switch-case語句是if語句第三種形态的優化版本,因為它的文法更簡潔。文法如下:
switch (表達式)
{
case 常量表達式1: 語句或程式塊
case 常量表達式2: 語句或程式塊
……
case 常量表達式n:語句或程式塊
default: 語句或程式塊
}
示例代碼:
#include<stdio.h>
int main() {
char grade;
printf("請輸入學生成績評級:");
scanf("%c",&grade);
switch(grade) {
case 'A':
printf("該學生分數是90分以上。\n");
/*break關鍵字用于跳出switch語句*/
break;
case 'B':
printf("該學生分數是80分以上。\n");
break;
case 'C':
printf("該學生分數是70分以上。\n");
break;
case 'D':
printf("該學生分數是60分以上。\n");
break;
case 'E':
printf("該學生分數未及格。\n");
break;
/*default語句是可選的,即可以不寫,不過最好寫上*/
default:
printf("輸出成績評級不合法,請輸入('A'/'B'/'C'/'D'/'E')。\n");
break;
}
return 0;
}
可以看出我們在switch語句中增加了break關鍵字用于跳出switch語句,如果不加break,switch語句就會從比對到的case開始執行,一直執行到default才會退出,這種現象叫做“case穿透”。switch語句中的 case和 default 事實上都是“标簽”,用來标志一個位置而已。當 switch 跳到某個位置之後,就會一直往下執行,是以我們這裡還需要配合一個 break語句,讓代碼在适當的位置跳出 switch。
來看一個利用case穿透的示例:
#include<stdio.h>
/*
根據使用者輸入的月份,比對相應的季節
*/
int main() {
/*月份*/
int month;
/*季節*/
char* season;
printf("請輸入月份:");
scanf("%d",&month);
switch(month) {
case 3:
case 4:
case 5:
season = "春季";
break;
case 6:
case 7:
case 8:
season = "夏季";
break;
case 9:
case 10:
case 11:
season = "秋季";
break;
case 12:
case 1:
case 2:
season = "冬季";
break;
default:
printf("輸出月份不合法。\n");
return 0;
}
printf("輸出的月份對應%s。\n",season);
return 0;
}
if和switch語句嵌套
#include<stdio.h>
/*
根據使用者輸入的月份,比對相應的季節
*/
int main() {
/*月份*/
int month;
/*季節*/
char* season;
printf("請輸入月份:");
scanf("%d",&month);
if(month >= 1 && month <= 12) {
switch(month) {
case 3:
case 4:
case 5:
season = "春季";
break;
case 6:
case 7:
case 8:
season = "夏季";
break;
case 9:
case 10:
case 11:
season = "秋季";
break;
case 12:
case 1:
case 2:
season = "冬季";
break;
default:
break;
}
printf("輸出的月份對應%s。\n",season);
} else {
printf("輸出的月份不合法!\n");
}
return 0;
}
if語句也可以與if嵌套,大家自行練習。
循環結構
while循環
文法:while(邏輯表達式){循環體} ------ 隻要邏輯表達式的結果為真,就會一直循環執行循環體中的語句。
/*
統計使用者輸入的字元個數:
*/
#include<stdio.h>
int main() {
int count = 0;
printf("請輸入你想說的話:");
/*getchar()------從标準輸入流中擷取字元*/
while(getchar() != '\n') {
count++;
}
printf("您總共輸入了%d個字元。\n",count);
}
do...while循環
文法:do{循環體}while(邏輯表達式); ------ 先會執行一次循環體,然後進入while,隻要邏輯表達式為真,就會一直循環執行。do...while循環語句不是很常用。
for循環
文法格式:
for (表達式1; 表達式2; 表達式3){
循環體;
}
表達式1是初始化循環變量;表達式2是循環的條件,滿足條件就會執行循環體;最後執行表達式3,表達式3是改變循環變量的值。下面是一個判斷使用者輸入的自然數是否是素數的案例:
/*
判斷使用者輸入的數字是否是素數:
<1>質數又稱素數。一個大于1的自然數,除了1和它自身外,不能被其他自然數整除的數叫做質數;否則稱為合數。
<2>0和1既不是質數也不是合數,最小的質數是2。
*/
#include<stdio.h>
int main() {
/*接收使用者輸入的自然數*/
int num;
/*标記是否是素數,預設不是素數*/
_Bool isPrimer = 1;
printf("請輸入一個自然數:");
scanf("%d",&num);
if(num == 0 || num == 1) {
printf("您輸入的%d既不是素數也不是合數~\n",num);
return 0;
}
/*已知2和3是素數,是以這裡隻對大于3的自然數做處理*/
if(num > 3) {
int i;
/*能整除一個數的數,肯定小于或等于被除數的一半,是以這裡取 num/2 */
for(i = 2; i <= num / 2; i++) {
if(num % i == 0) {
isPrimer = 0;
/*隻要找到一個可以整除num的除數就跳出循環*/
break;
}
}
}
if(isPrimer)
printf("您輸入的自然數%d是素數\n",num);
else
printf("您輸入的自然數%d不是素數\n",num);
return 0;
}
另外,for 語句的表達式1,表達式2和表達式3都可以按需省略(但分号不能省):
- for ( ; 表達式2; 表達式3)
- for (表達式1; 表達式2; )
- for (表達式1; ; )
- for ( ; ; )
- ……
C99允許在 for語句的表達式1中定義變量,比如如下代碼:
for (int i=0, int j=10; i < j; i++, j--){
printf("%d\n", i);
}
增加這個新特性的原因主要是考慮到循環通常需要一個循環變量(計數器),而這個循環變量隻需要在for循環中有效即可。是以在表達式1的位置定義的循環變量,作用域僅限于循環中,出了循環,它就無效了。
ps:循環結構同意可以嵌套,如下中的for循環嵌套案例,先執行内層循環,再執行外層循環。
/*
列印九九乘法表
*/
#include<stdio.h>
int main() {
int i,j;
for(i=1; i<10; i++) {
for(j=1; j<=i; j++) {
printf("%d * %d = %d\t",j,i,j*i);
}
printf("\n");
}
return 0;
}
continue
continue與break都是用于跳出循環,continue是跳出本輪循環繼續下一輪循環,break是跳出所在的那一層循環(當循環嵌套時如果要跳出整個循環可以借助goto語句,其他場景慎用goto)。
/*
去掉使用者輸入的空格
*/
#include<stdio.h>
int main() {
char ch;
while((ch = getchar()) != '\n') {
if(ch == ' ') {
continue;
}
putchar(ch);
}
putchar('\n');
return 0;
}
多學一點
for VS while
for語句和 while語句執行過程是有差別的,它們的差別在于出現 continue語句時。在 for語句中,continue語句跳過循環的剩餘部分,直接回到調整循環變量部分。在 while語句中,由于調整部分是循環體的一部分,是以 continue語句會把它也跳過,稍不注意就會出現問題。比如如下案例中:使用while改造for循環實作的功能,結果死循環了{T_T}
/*
for VS while
*/
#include<stdio.h>
int main() {
int i,j;
for(i = 0,j = 10; i<10; i++,j--) {
if(i > j) {
continue;
}
}
printf("for循環執行完成後i和j的值分别是:%d、%d\n",i,j);
/*
使用while改造上面的for循環
*/
i = 0;
j = 10;
while(i<10) {
if(i > j) {
continue;
}
i++;
j--;
}
printf("while循環執行完成後i和j的值分别是:%d、%d\n",i,j);
return 0;
}
goto
goto用于跳到指定标簽的位置。
/*
goto語句使用案例
*/
#include <stdio.h>
int main() {
int i = 6;
while (i++) {
if (i > 8) {
goto Label;
}
}
Label:
printf("第%d次發牢騷:\n從來表白多白表,\n自古情書難書情,\n笑談年少多少年,\n常與生人道人生。 \n", i);
return 0;
}
條件運算符
條件運算符是C語言中唯一一個三目運算符,文法格式:exp1 ? exp2 : exp3; exp1是邏輯表達式,如果結果為真,則取exp2的值,否則取exp3的值。其實它就是為了簡化 if...else...這種簡單的語句。
/*
條件運算符簡化if-else語句,實作求兩個數中的最大值。
*/
#include <stdio.h>
int main() {
int i = 6,j = 8,max;
if(i > j)
max = i;
else
max = j;
/*使用條件表達式簡化上述語句*/
max = i > j ? i:j;
printf("最大值是%d。\n",max);
return 0;
}
彩蛋
#include <stdio.h>
int main()
{
int a = 3;
printf("%d,%d\n",a++,a++);
printf("\n%d",a);
return 0;
}