主頁 > 軟體設計 > 【C語言學習之路】第一篇:與C語言的初次見面

【C語言學習之路】第一篇:與C語言的初次見面

2021-04-25 11:50:36 軟體設計


閱前說明

C語言學習之路系列博客,是博主自己在學習C語言的程序中所做的筆記,把知識框架整理記錄下來,為了后續回顧與復習,同時也希望該博客可以幫助到一些正在學習C語言的小伙伴,博客內容如有錯誤或疏漏,請大家指出,謝謝啦,博主一定多多學習,及時改正過來,

使用的編譯器:Visual Studio 2019(下載地址)


本篇前言

本篇大致介紹了C語言的基礎知識,希望能讓大家對C語言有一個大概的認識,每個知識點只是簡單介紹,后續篇章都會一一詳細講解,


點擊可直接跳轉

文章目錄

    • @[toc]
    • (1)什么是C語言?
      • 第一個C語言程式
    • (2)資料型別
      • 每種型別的大小是多少?
      • 計算機中的單位
      • 計算機中的進制
    • (3)變數、常量
      • 變數的定義
      • 變數的分類
      • 變數的作用域和生命周期
      • 常量
    • (4)字串、轉義字符、注釋
      • 字串
        • 1、什么是字串:
        • 2、字串的存盤:
        • 3、字串的結束標志:
        • 4、獲取字串的長度:
        • 5、計算字符陣列/陣列的長度:
        • 6、一定要區分開 '0' 和 '\0' 和 0
      • 轉義字符
        • 1、什么是轉義字符
        • 2、C語言中有哪些轉義字符
        • 3、轉義字符的應用
        • 4、ASCII編碼
        • 5、測驗題:
      • 注釋
    • (5)運算子
      • 算數運算子
      • 移位運算子
      • 位運算子
      • 賦值運算子
      • 單目運算子
      • 關系運算子
      • 邏輯運算子
      • 條件運算子
      • 逗號運算式
      • 下標參考、函式呼叫、結構成員
    • (6)常見關鍵字
      • 1、關鍵字 `typedef`
      • 2、關鍵字 `static`
        • `static` 修飾區域變數
        • `static` 修飾全域變數
        • `static` 修飾函式
    • (7)`#define` 定義宏
    • (8)選擇陳述句
    • (9)回圈陳述句
    • (10)函式
    • (11)陣列
    • (12)指標
        • 1、什么是記憶體
        • 2、獲取變數的地址
        • 3、地址的存盤 - 指標變數
        • 4、指標變數的大小
    • (13)結構體

(1)什么是C語言?

C語言廣泛應用于底層開發(不做詳細介紹,可百度)

image-20210424180255962
  • 計算機語言發展程序

    • 二進制語言 0/1
  • 匯編語言 - 助記符 ADD/AND

    • 高級語言

第一個C語言程式

//test.c(testcode project)
#include <stdio.h>
int main()
{
  printf("hello C language\n");
  return 0;
}

main函式是程式的入口

一個工程/專案可以有多個.c檔案,但只能有一個main函式


(2)資料型別

char     //字符資料型別 
short    //短整型 
int     //整型
long     //長整型 
long long  //更長的整型 
float    //單精度浮點數 
double    //雙精度浮點數
  • C語言中沒有沒有字串型別?

    沒有

  • 為什么會出現這么多型別?

    更加豐富的表達生活中的各種值,可以提高空間利用率

每種型別的大小是多少?

這里需要用到 sizeof() 運算子 - 獲取型別或者變數所占空間大小(以位元組為單位)

#include<stdio.h>
int main()
{
	//每種型別的大小是多少?
	printf("%d位元組\n", sizeof(int));
	printf("%d位元組\n", sizeof(short));
	printf("%d位元組\n", sizeof(long));
	printf("%d位元組\n", sizeof(long long));
	printf("%d位元組\n", sizeof(char));
	printf("%d位元組\n", sizeof(float));
	printf("%d位元組\n", sizeof(double));
	printf("%d位元組\n", sizeof(long double));
	return 0;
}

運行結果(x86平臺):

C語言標準:規定只要sizeof(long)>=sizeof(int)就可以了,不需要一定大于

計算機中的單位

bit - 位元位 - 一個位元位存放一個二進制位 0/1

byte - 位元組 - 一個位元組 = 8bit

kb - 1kb = 1024byte

mb - 1mb = 1024kb

計算機中的進制

二進制:0 1

八進制:0 ~ 7(三位二進制等于一位八進制)

十進制:0 ~ 9

十六進制:0 ~ 9 , A ~ F(四位二進制等于一位十六進制)


(3)變數、常量

變數的定義

注:建議定義變數時給它初始化一個值

#include<stdio.h>
int main()
{
	//推薦定義變數時給它初始化一個值,比如:
	int age = 0;
	double weight = 52.3;
	return 0;
}

變數的分類

  • 區域變數
  • 全域變數(不提倡使用,后續講原因)
#include<stdio.h>
int a = 100;  //全域變數 - { }外部定義的
int main()
{
	int a = 10;  //區域變數 - { }內部定義的
	printf("a = %d", a);  //輸出a = 10
	return 0;
}

思考:為什么會輸出 a = 10 呢?

當區域變數和全域變數名稱沖突時,區域優先

不建議把全域變數和區域變數的名字寫成一樣(如上面代碼中的 a)


變數的作用域和生命周期

變數的作用域

這個變數可以在哪里使用,哪里就是它的作用域

  • 區域變數的作用域

    變數所在的區域范圍

#include<stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", a);
    
	{
		int b = 20;  //b的作用域為此 {} 內
	}
	//printf("%d\n", b);  //error: "b"是未宣告的識別符號
	return 0;
}
  • 全域變數的作用域

    整個工程(在同一工程的其它檔案下使用需要用 extern 宣告一下變數)

/*源檔案demo2.c(test_4_5 project)*/

int x=5;
/*源檔案demo.c(test_4_5 project)*/

#include<stdio.h>
int main()
{
    //宣告 demo2.c 檔案中的全域變數
    extern int x;
    
    printf("%d\n", x);
    return 0;
}

變數的生命周期

變數的創建到銷毀之間的時間段

//區域變數的生命周期:進入區域范圍生命開始,出區域范圍生命結束
//全域變數的生命周期:就是整個程式的生命周期

#include<stdio.h>
int main()  //一個程式的生命周期就是 main 函式的生命周期
{
	{  //變數 a 宣告開始
		int a = 10;
		printf("%d\n", a);
	}  //變數 a 宣告結束
	return 0;
}

常量

C語言中常量分為以下幾種:

  • 字面常量
  • const修飾的常變數
  • #define定義的識別符號常量
  • 列舉常量

1、字面常量

//字面常量
3.14;
10;
'a';  //字符常量 - 由一對單引號引起來的單個字符
"abcdef";  //字串常量

2、const修飾的常變數

常變數 - 具有常屬性(不能被改變的屬性) - 本質上還是變數,不能當常量使用

這樣是錯誤的(只有在C99語法下才支持變長陣列)

image-20210406174622468

這樣是可以的

int arr[10] = { 0 };
const int N = 9;
//思考:這兩種寫法正確嗎?
arr[N] = 10;  //這樣是可以的
N = 20;  //error

3、#define 定義的識別符號常量

使用 #define 指令為程式中的常量賦予有意義的名稱,它只是一個符號,在預編譯階段進行字符替換

(如下:所有 MAX 符號將被替換成 100)

#include<stdio.h>
#define MAX 100  //#define 定義的識別符號常量
int main()
{
    MAX = 200;  //error
    return 0;
}

4、列舉常量

列舉常量 - 可以一一列舉的常量:比如星期、三原色、性別

//定義列舉常量Sex
enum Sex
{
	//列舉常量默認的值: 0 1 2 3 …… 依次遞增 - 其值不可被更改
	Male,    // 0
	Female,  // 1
	Secret   // 2
};

#include<stdio.h>
int main()
{
	enum Sex S1 = Male;
	printf("%d\n", Male);  	 //輸出:0
	printf("%d\n", Female);  //輸出:1
	printf("%d\n", Secret);  //輸出:2
	return 0;
}

列舉常量不可以賦值,但可以在定義的時候 指定值,這個可以認為是定義值,而不是賦值,

enum Sex
{
	//這個可不是賦值哦
	Male = 3,
	Female = 100,
	Secret
};
printf("%d\n", Male);
printf("%d\n", Female);
printf("%d\n", Secret);

列印其值,運行結果為:
3
100
101


(4)字串、轉義字符、注釋

字串

1、什么是字串:

字串就是由雙引號引起來的一串字符

"hello world"

2、字串的存盤:

因為C語言沒有字串這種資料型別,所以要用到「 字符陣列 」來存盤字串(每個元素都是字符型別的陣列)

#include<stdio.h>
int main()
{
	char arr[] = "hello";
    //['h']['e']['l']['l']['o']['\0']
	return 0;
}

3、字串的結束標志:

C語言規定,在每一個字串常量的結尾,系統都會自動加一個字符 ‘\0’ 作為該字串的“結束標志符”,系統據此判斷字串是否結束,計算字串長度時,’\0’ 不算作字串的內容哦;但是計算陣列長度(或元素個數或所占記憶體空間大小),’\0’ 要算進去,

通過除錯,打開監視視窗可看到字符陣列內部情況

image-20210414194135679 image-20210414193943566
  • 接下來我們用代碼來驗證一下:
#include<stdio.h>
int main()
{
	char arr1[] = "abc";  //字串
	char arr2[] = { 'a','b','c' };  //是字符陣列,但不代表字串,末尾沒有'\0'
	
	printf("%s\n", arr1);
	printf("%s\n", arr2);
	return 0;
}

除錯這段代碼,我們發現,arr1 陣列尾部有結束標志 ‘\0’ ,而 arr2 陣列后面卻沒有 ‘\0’

image-20210414195943309

再來看看運行結果,arr1 正常輸出,而 arr2 后面卻亂碼了,輸出了一堆奇怪的符號,這是為什么呢?

因為在輸出 arr1 中的內容程序中,遇到了 ‘\0’ 是字串的結束標志,所以停止輸出;

而輸出 arr2 中的內容時,輸出完 abc 沒有遇到字串結束標志 ‘\0’ ,不知道什么時候停下來,而后面的記憶體空間的內容是未知的,放的是啥我們不知道,所以輸出亂碼,

![](https://img-blog.csdnimg.cn/img_convert/3984e84c9a5a3f9e3c9507928fe6bd98.png style="zoom:80%)

現在我們修改代碼,主動在 arr2 后面放上 ‘\0’ ,輸出看看效果

#include<stdio.h>
int main()
{
    //定義并初始化字串的兩種方式
    //一定不要忘記結束標志 '\0' 了哦
	char arr1[] = "abc";
	char arr2[] = { 'a','b','c','\0' };
    
	printf("%s\n", arr1);
	printf("%s\n", arr2);
	return 0;
}

運行結果:都能列印出 abc 由此可見,字串的結束標志是至關重要的

image-20210414202940562

4、獲取字串的長度:

這里要使用到一個函式 strlen() - string length - 呼叫該函式需要參考頭檔案<string.h>

#include<stdio.h>
#include<string.h>  //呼叫strlen()函式
int main()
{
	char arr1[] = "abc";
	char arr2[] = { 'a','b','c' };
	char arr3[] = { 'a','b','c','\0' };
	//列印字串的長度
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	printf("%d\n", strlen(arr3));
	return 0;
}

運行結果:獲取字串的長度并輸出時,字符陣列 arr2 因為末尾沒有字串結束標志 ‘\0’ ,不知道長度到底是多少,所以輸出隨機值,獲取不了字串的長度,

注:在計算字串長度的時候 ‘\0’ 是結束標志,不算作字串的內容,

image-20210416210559941

5、計算字符陣列/陣列的長度:

計算陣列長度(或元素個數或所占記憶體空間大小), ‘\0’ 要算進去

#include<stdio.h>
int main()
{
	char arr[] = "abc";
	int len = 0;
    //計算陣列 arr 的長度:陣列記憶體總大小(位元組)/單個陣列元素記憶體大小(位元組)
	len = sizeof(arr) / sizeof(arr[0]);
	
    printf("len = %d\n", len);
	return 0;
}

運行結果:4(因為字符陣列 arr 后面還有一個系統自動添加的結束符 ‘\0’ )

6、一定要區分開 ‘0’ 和 ‘\0’ 和 0

字符 ‘0’ —— ASCII碼值為 48

轉義字符 ‘\0’ —— 字串的結束標志(不算作字串的內容) - ASCII碼值為 0

數字 0


轉義字符

1、什么是轉義字符

轉義字符顧名思義就是轉變了字符原來的意思

來段代碼感受一下,如果我們想要在螢屏上列印一個目錄:c:\test\test.c

#include<stdio.h>
int main()
{
	printf("c:\test\test.c");
	return 0;
}

運行結果:很奇怪,和我們想象的不一樣唉,沒有列印出目錄,卻出現了兩段空白,這個字符\和字符t不再是普通的字符的意思了,轉變了原來的意思,變成了轉義字符\t(水平制表符)

image-20210415224750303

同理,字符\和字符n也不再是兩個普通的字符了,而是轉義字符\n(換行符)

image-20210415225644519

2、C語言中有哪些轉義字符

轉義字符釋義
\’用于表示字符常量'
\"用于表示一個字串內部的雙引號"
\\用于表示一個反斜杠,防止它被解釋為一個轉義序列符
\a警告字符,蜂鳴
\n換行
\r回車
\t水平制表符(鍵盤上的 Tab 鍵)
\v垂直制表符
\dddddd表示 1-3 個八進制的數字,如:\130(從 \000 到 \377)
\xdddd表示 2 個十六進制的數字,如:\x30
\b退格符
\f進制符
?在書寫連續多個問號時使用,防止他們被決議成三字母詞

3、轉義字符的應用

  • 思考問題:在螢屏上列印一個單引號'怎么做?

大家再沒有學習轉義字符之前可能會想直接輸出列印就好啦,而寫成下面這個錯誤示范了哦

錯誤示范:中間的你想要輸出的那個單引號 ’ 和左邊的單引號組成一對了,編譯會 error

printf("%c\n", ''');

修改正確:為了防止其與左邊的單引號組成一對,需要在單引號前加一個轉義符

printf("%c\n", '\'');  //這里 \'被決議成一個轉義字符

同理:在螢屏上列印一個雙引號"就可以這樣寫啦

printf("%s\n", "\"");
  • 回到最初的問題:在螢屏上列印一個c:\test\test.c怎么做?
printf("c:\\test\\test.c");
  • \ddd 與 \xdd :使用編碼值來間接的表示字符
//輸出字符 A - 對應的ASCII碼的8進制形式是101 - 8進制的130是十進制的65
printf("%c\n", '\101');

//輸出字符 a - 對應的ASCII碼的8進制形式是141 - 8進制的141是十進制的97
printf("%c\n", '\141');

//輸出字符 B - 對應的ASCII碼的16進制形式是42
printf("%c\n", '\x42');

//輸出字符 b - 對應的ASCII碼的16進制形式是62
printf("%c\n", '\x62');

對于轉義字符來說,只能使用八進制或者十六進制,

轉義字符的初衷是用于 ASCII 編碼,所以它的取值范圍有限:

  • 八進制形式的轉義字符最多后跟三個數字,也即\ddd,最大取值是\177
  • 十六進制形式的轉義字符最多后跟兩個數字,也即\xdd,最大取值是\x7F

@參考文章:C語言轉義字符

4、ASCII編碼

補充知識:

計算機只認識 0 和 1 兩個數字,資料在記憶體中以二進制形式存盤,我們在螢屏上看到的文字,在存盤之前都被轉換成了二進制(0和1序列),在顯示時也要根據二進制找到對應的字符,

那么,怎么將英文字母和二進制對應起來呢?這就需要有一套規范,一種專門針對英文的字符集 - ASCII編碼就被設計出來了,為每個字符分配了唯一的編碼值在C語言中,一個字符除了可以用它的物體表示,還可以用編碼值表示,使用編碼值來間接地表示字符的方式稱為轉義字符,

在 ASCII 編碼中,大寫字母、小寫字母和阿拉伯數字都是連續分布的(見下表),這給程式設計帶來了很大的方便,例如要判斷一個字符是否是大寫字母,就可以判斷該字符的 ASCII 編碼值是否在 65~90 的范圍內,

@參考文章:ASCII編碼,將英文存盤到計算機

ASCII碼表

此圖轉載自百度

5、測驗題:

#include<stdio.h>
#include<string.h>
int main()
{
	//程式輸出什么呢?
	printf("%d\n", strlen("c:\test\328\test.c"));
	return 0;
}

運行結果:14(\t\32分別是一個轉義字符)


注釋

注釋有兩種風格:

在撰寫C語言源代碼時,應該多使用注釋,這樣有助于對代碼的理解,

  • C語言風格的注釋(缺陷:不能嵌套注釋)
/*xxxxxxxxxxxx*/
  • C++風格的注釋(可以注釋一行也可以注釋多行)
//xxxxxxxxxxxx

(5)運算子

這里只是簡單介紹一下各種運算子,目的是能夠讓大家在代碼中識別出這些,后面我們會詳細講解和學習

算數運算子

+ - * / %

移位運算子

移位運算子含義
>>右移運算子 - 把二進制位向右移動 - 缺位補 0
<<左移運算子 - 把二進制位向左移動 - 缺位補 0

舉例說明:

int a = 1;
int b = a << 1;
//a - 00000000000000000000000000000001 --> a = 1
//b - 00000000000000000000000000000010 --> b = 2

位運算子

位運算子含義
&按位與
|按位或
^按位異或

賦值運算子

=  //賦值符
+=  -=  *=  /=  &=  ^=  |=  >>=  <<=  //復合賦值符

舉例說明:

int a = 0;
a = a + 1;
//等效于: 
a += 1;

單目運算子

單目運算子含義
!邏輯反操作
-負值
+正值
&取地址
sizeof運算元的型別長度(以位元組為單位)
~對一個數的二進制序列按位取反
前置、后置–
++前置、后置++
*間接訪問運算子(解參考運算子)
(型別)強制型別轉換

注:

雙目運算子:有兩個運算元(比如:a + b)

單目運算子:只有一個運算元

補充知識:C語言中如何表示真和假?

0 表示假,非 0 表示真

下面來展開講解幾個運算子

  1. 按(二進制)位取反運算子:~

對一個數的所有二進制位取反,0 變成 1,1 變成 0

#include<stdio.h>
int main()
{
	int a = 0;
	int b = ~a;  //按位取反
	printf("%d\n", b);  //輸出的是 b 的原碼
	return 0;
}

運行結果:-1

這個時候大家可能會有疑問了,為啥會是 -1 呀,別著急,仔細聽我道來

這里需要引入原碼 反碼 補碼的概念

整數在記憶體中存盤的都是二進制形式的「 補碼 」,輸出列印時 要將其轉換成「 原碼 」

「 原碼 」第一位是「 符號位 」表示 「 正負 」(1為負,0為正)

所以:

a - 00000000000000000000000000000000(補碼)

對 a 的二進制位( ~ 按位取反)得到 b 的二進制形式的「 補碼 」,因為輸出的是「 原碼 」所以我們進一步轉換:

b - 11111111111111111111111111111111(補碼)

b - 11111111111111111111111111111110(補碼 -1得到反碼)

b - 10000000000000000000000000000001(符號位不變,其它位按位取反得到原碼)

而二進制序列 10000000000000000000000000000001 就是 -1

知識點:

整數(正數、負數)的原碼反碼補碼:

  • 正數:原碼 = 反碼 = 補碼 相同
int i = 2;
00000000000000000000000000000010 - 原碼
00000000000000000000000000000010 - 反碼
00000000000000000000000000000010 - 補碼
  • 負數
二進制形式轉換規則
原碼按照整數正負 寫出二進制序列
反碼「 原碼 」符號位不變 其它位按位取反
補碼反碼 + 1
int i = -2;
10000000000000000000000000000010 - 原碼
11111111111111111111111111111101 - 反碼
11111111111111111111111111111110 - 補碼
  1. **自增自減運算子:++ – **
#include<stdio.h>
int main()
{
	int a = 10;
	int b = ++a;  //前置++:先++ 后使用
	int c = a++;  //后置++:先使用 后++

	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

前置后置-- 同理

同時建議大家不要去研究這樣的代碼,浪費時間,在不同的編譯器得到的結果都不一樣,平時開發也幾乎不會這樣去寫,可讀性太差

int a = 1;
int b = (++a) + (++a) + (a++);
  1. 強制型別轉換:(型別)

一般在型別不匹配的時候使用,不推薦,既然你會使用強制型別轉換,說明一開始在撰寫程式的時候就沒有設計好各變數的資料型別,導致程式有缺陷

int a = (int)3.14;

關系運算子

>
>=
<
<=
!=  用于測驗“不相等”
==  用于測驗“相等”

邏輯運算子

描述并且、或者的關系

邏輯 與 / 或 的結果為:真 / 假

&&   邏輯與
||   邏輯或

條件運算子

運算式 exp1 成立,整個運算式的結果是 exp2 的結果

運算式 exp1 不成立,整個運算式的結果是 exp3 的結果

exp1 ? exp2 : exp3  //也是三目運算子,有三個運算元

舉例說明:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int max = a > b ? a : b;
	printf("max = %d\n", max);
	return 0;
}

運行結果:max = 20

逗號運算式

逗號隔開的一串運算式,從左向右依次計算的,整個運算式的結果是最后一個運算式的結果

exp1, exp2, exp3, …expN

舉例說明:

#include<stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = 5;
    //思考 d 的值是多少?
	int d = (a = b + 2, c = a - 4, b = c + 2);
	printf("d = %d\n", d);
	return 0;
}

運行結果:d = 2

思考題:

函式呼叫 exec( ( vl, v2 ), ( v3, v4 ), v5, v6 ); 中,實參的個數是:( 4 ) - 分別是v2 v4 v5 v6

下標參考、函式呼叫、結構成員

[]	下標參考運算子
()	函式呼叫運算子
.	結構體成員訪問運算子
->

舉例說明

image-20210418211613332

(6)常見關鍵字

1.關鍵字是C語言提供的,不能自己創建關鍵字
2.關鍵字不能做變數名

auto(修飾區域變數,一般是省略的)  break   case  char  const   continue  default  
do   double	  else  enum(列舉)  extern(宣告外部符號)  float  for   goto  if   int   
long  register(暫存器)   return   short   signed(有符號數)   unsigned(無符號數)  
sizeof(計算機型別/變數所占記憶體空間大小)   static(靜態)   struct(結構體)   switch  
typedef(型別定義)   union(聯合體/共用體)   void   volatile(C語言中暫時不講)   while
  • 補充知識:

計算機中,資料可以存放到哪里呢?

暫存器(會被頻繁呼叫的資料,放在暫存器中,可以提高效率,現在編譯器已經能夠自動識別這種資料并放在其中,register實用意義不大)

高速快取

記憶體

硬碟

  • 思考:#define是不是關鍵字?include是不是關鍵字?

都不是,它們是預處理指令(預編譯期間處理的指令)

關鍵字這次先簡單介紹幾個,后續遇到了再講解

1、關鍵字 typedef

型別重定義,型別重命名,相當于取了一個別名

舉例說明:

#include<stdio.h>

//把 unsigned int 重命名為 u_int,現在 u_int 也是一個型別名了
typedef unsigned int u_int;
int main()
{
    //a 和 b 的型別相同
	unsigned int a = 10;
	u_int b = 20;
	
    return 0;
}

2、關鍵字 static

static 用來修飾變數和函式

1)修飾區域變數 - 靜態區域變數

2)修飾全域變數 - 靜態全域變數

3)修飾函式 - 靜態函式

static 修飾區域變數

改變了區域變數的生命周期(本質上是變數的存盤型別),讓靜態區域變數出了作用域依然存在,直到程式結束生命周期才結束

代碼1:

#include<stdio.h>
void test()
{
	int a = 1;  //區域變數 a
	a++;
	printf("%d ", a);
}
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		test();
	}
	return 0;
}

運行結果:2 2 2 2 2 2 2 2 2 2(10個2)

代碼2:

#include<stdio.h>
void test()
{
	static int a = 1;  //靜態區域變數a
	a++;
	printf("%d ", a);
}
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		test();
	}
	return 0;
}

運行結果:2 3 4 5 6 7 8 9 10 11

為啥會出現這樣的結果呢?

這里補充一點小知識,記憶體會被分為幾個區域,今天在這里我們只討論和這次C語言有關的幾個區域,區域變數 a 開始放在堆疊區的,被 static 修飾后就被放到靜態區去了,改變了它的存盤型別,存在不同的區域,就有不同的特點了,靜態變數 a 的生命周期和全域變數一樣,為整個程式

image-20210422113845034

static 修飾全域變數

static修飾全域變數,使得這個全域變數只能在自己所在的源檔案(.c)內使用

不能在同一工程的其他源檔案內使用

代碼實體:

/*源檔案demo2.c(test project)*/

//靜態全域變數g_val
static int g_val = 2021;
/*源檔案demo.c(test project)*/

#include<stdio.h>
//宣告靜態全域變數 g_val
extern int g_val;
int main()
{
	printf("%d\n", g_val);
	return 0;
}

運行結果:error

image-20210422123125561

運行程式,結果error了,那么 static 修飾全域變數的本質是什么呢?

全域變數,可以在同一工程的其他源檔案內被使用,是因為全域變數具有外部鏈接屬性

但是當被 static 修飾成靜態全域變數,就變成了內部鏈接屬性,其他源檔案就不能鏈接到這個靜態全域變數了

所以無法被其他源檔案所使用,只能在自己所在的源檔案內使用

static 修飾函式

static修飾函式,使得這個函式只能在自己所在的源檔案(.c)內使用

不能在同一工程的其他源檔案內使用

本質上是:static 將函式的外部鏈接屬性變成了內部鏈接屬性(和 static 修飾全域變數一樣)

仔細對比代碼1和代碼2,理解 static 在修飾函式前后的意義

代碼1:

image-20210423112350703

代碼2:

image-20210423113111570

(7)#define 定義宏

#define 是C語言提供的宏定義命令,它用來將一個識別符號定義為一個字串,該識別符號被稱為宏名,被定義的字串稱為替換文本,該命令有兩種格式:一種是簡單的宏定義,另一種是帶引數的宏定義,

在該程式被編譯前,先將宏名用被定義的字串替換,這稱為宏替換,替換后才進行編譯,宏替換是簡單的替換,

還有就是要記住這是簡單的替換而已,不要在中間計算結果,一定要替換出運算式之后再算,

//普通宏
#define PI (3.1415926)
//帶引數的宏
#define ADD(a,b) ( (a) + (b) )
//關鍵是十分容易產生錯誤,包括機器和人理解上的差異等等

來看一個例子:

int a = 2 * ADD(2, 3);

預處理階段之后,ADD(2, 3) 則會被替換成 ( (2) + (3) )

int a = 2 * ( (2) + (3) );  //結果為 10

如果沒有括號

#define ADD(a,b) a + b

替換之后的結果是:

int a = 2 * 2 + 3;  //結果為 7

(8)選擇陳述句

為了先將C語言拉通介紹一遍,能夠對其有一個整體的認識,這里的內容只進行一個簡單的介紹,后續會詳細講解

生活中的選擇無處不在,那么在計算機中,怎么用C程式設計語言表示呢?

如果(if)就……

否則(else)就……

這就是選擇!

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	if (a > b)
	{
		printf("較大值:a\n");
	}
	else
	{
		printf("較小值:b\n");
	}
	return 0;
}

如果 a 比 b 大,輸出 a,否則就輸出 b


(9)回圈陳述句

生活中有些事,要一直做,重復做,比如學習、吃飯、睡覺等等,如何用C語言實作回圈呢?

  • while 陳述句
  • for 陳述句
  • do…while 陳述句

后面會詳細講解


(10)函式

假設(我是 main函式)(張三是 add 函式),有一天,我想讓張三給我帶飯,所以得告訴他我想帶什么飯,并給他帶飯的錢,(蛋炒飯,10元),張三接收到資訊和拿到錢,最后回傳(return)給我一份蛋炒飯

實體:用函式實作求兩數的和

#include<stdio.h>

int add(int x, int y)  //求兩數的和
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;
	int sum = add(a, b);
	printf("sum = %d\n", sum);
	return 0;
}

函式的特點就是簡化代碼,代碼復用


(11)陣列

我們需要存盤 10 個數字,怎么辦呢?如果去定義 10 個變數來存盤,那也太麻煩了,這時我們可以用陣列

C語言中陣列的定義:一組相同資料型別的元素的集合

  • 陣列的定義與初始化
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };  //完全初始化
int arr2[5] = { 1,2,3 };  //不完全初始化,剩余的默認為 '\0'
  • 陣列元素的訪問(通過陣列下標訪問,陣列的下標是從 0 開始的)
arr1[0] = 1;
arr1[1] = 2;
……
arr1[9] = 10;

(12)指標

指標是C語言中非常非常重要的內容,我們要談指標,必須先要搞明白記憶體

1、什么是記憶體

記憶體是電腦上特別重要的存盤器,計算機中所有程式的運行都是在記憶體中進行的,

為了有效的使用記憶體,就把記憶體劃分成一個個小的記憶體單元,「 每個記憶體單元的大小是1個位元組 」,

為了能夠有效的訪問到記憶體的每個單元,就給記憶體單元進行了編號,這些編號被稱為該「 記憶體單元的地址 」,

image-20210423144058148
  • 記憶體單元是如何編號的?

電腦分為 32位 和 64位 的,以 32位 舉例

32位 - 有32根地址線 - 通電會產生 1/0 電信號 - 電信號轉換成數字信號 - 變成32個 1/0 組成的二進制序列

共有 2 (32) = 4,294,967,296 種不同的序列

00000000000000000000000000000000
00000000000000000000000000000001
…………
01111111111111111111111111111111
11111111111111111111111111111111

我們就可以用這些二進制序列來給記憶體單元編號,一個位元組的編號就會由32個0/1組成,這就是它們的地址,

記憶體地址只是一個編號,代表一個記憶體空間,在計算機中存盤器的容量是以位元組為基本單位的,也就是說一個記憶體地址代表一個位元組(8bit)的存盤空間,例如經常說 32位 的作業系統最多支持4GB的記憶體空間,也就是說CPU只能尋址2的32次方(4GB)空間,

類比到生活中,一棟宿舍樓相當于一塊記憶體區域,被劃分成了一個個宿舍,為了方便管理和找到某一個宿舍,我們給每一間宿舍都編了門牌號,比如307,表示3樓7號,這樣很容易就找到它們的位置在哪里了,

2、獲取變數的地址

#include<stdio.h>
int main()
{
	//num在記憶體要被分配 4位元組 的記憶體空間
	int num = 10;
	//%p - 以地址的形式列印
	printf("%p\n", &num);
	return 0;
}

運行結果:004FFD64(這個地址是變數num所在記憶體空間的第一個位元組的地址)

image-20210423224308218

在VS2019中,F10進入除錯模式,選擇除錯選單 - 視窗 - 記憶體,就可以找到記憶體視窗

image-20210423181012823

輸入 &變數名,取出變數的地址,這個地址是變數所在記憶體空間的第一個位元組的地址

image-20210423182205615

3、地址的存盤 - 指標變數

指標變數存放相同資料型別的變數的首地址

//定義一個整型指標變數,存盤整型變數num的地址
int num = 10;
int* p = &num;
//字符型指標變數指向字符型變數
char ch = 'b';
char* str = &ch;

指標變數的使用舉例:通過指標變數存放的變數的地址,找到所指向的變數

#include<stdio.h>
int main()
{
	int num = 10;
    //指標變數 p
	int* p = &num;
    //* 是 解參考運算子 / 間接訪問運算子,訪問指標 p 指向的記憶體空間
	*p = 20;
	
    printf("num = %d\n", num);
	return 0;
}

運行結果:num = 20

4、指標變數的大小

任何型別的指標變數在 32位平臺 是4個位元組大小,64位平臺 是8個位元組大小

指標變數占幾個位元組跟語言無關,取決于變數的地址的存盤需要多大的空間,進一步講,是與系統的尋址能力有關*(提示:結合上面內容 - 1、什么是記憶體 - 記憶體是如何編號的? - 進行理解)*

32位系統 - 32根地址總線 - 一個地址是 32 個位元位 - 4位元組

64位系統 - 64根地址總線 - 一個地址是 64 個位元位 - 8位元組

所以,不管你是整型變數還是字符型變數還是浮點型變數,在 32/64 位平臺中,你的第一個位元組(首地址)的編址是由 32/64 個位元位組成的二進制序列,占 4/8 個位元組

image-20210424163446947
#include<stdio.h>
int main()
{
	printf("%d位元組\n", sizeof(char *));
	printf("%d位元組\n", sizeof(int *));
	printf("%d位元組\n", sizeof(double *));
	return 0;
}

運行結果:在(32位平臺)均輸出 4位元組、在(64位平臺)均輸出 8位元組

  • VS2019中,這里可以切換程式運行的平臺 x86(32位) / x64(64位)
image-20210424165217713

(13)結構體

結構體是C語言中特別重要的內容,結構體使得C語言可以描述復雜型別

比如描述一個人,用之前學過的任何一種型別變數都不足以描述清楚,int? float? char?,似乎都沒內味,人是一個復雜物件,有姓名,年齡,性別等等;描述一本書,有作者,定價,書類等等

而我們用結構體,就可以創造一些型別,來描述這些復雜型別了

接下來,我們來創建一個學生的結構體

struct Student
{
	char name[20];  //姓名
	int age;  //年齡
	double grade;  //成績
};

結構體變數的定義與初始化

  • 定義時初始化
  • 定義之后初始化

一般在定義時初始化,比較方便;定義之后初始化,就只能對每一個成員一一賦值初始化了

//定義一個結構體變數并初始化
struct Student s1 = { "瑪卡巴卡",20,85 };
//定義之后初始化
strcpy(s1.name, "李四");  //因為name是陣列名,它是一個地址,不能用 s1.name = "李四";
						 //strcpy - 字串拷貝函式 - 庫函式 - 參考頭檔案string.h
s1.age = 25;
s1.grade = 90;

訪問結構體變數中的成員

//方法(1):結構體變數.結構體成員
printf("name: %s age: %d grade: %lf\n", s1.name, s1.age, s1.grade);
	
//方法(2):結構體指標->結構體成員
struct Student* ps = &s1;
printf("name: %s age: %d grade: %lf\n", ps->name, ps->age, ps->grade);

//方法(3):太麻煩,建議用 ->運算子 訪問更直觀
printf("name: %s age: %d grade: %lf\n", (*ps).name, (*ps).age, (*ps).grade);

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/279856.html

標籤:其他

上一篇:Java基礎語法(七)——類和物件

下一篇:堆疊的綜合應用:數的轉換,括號匹配的檢驗,行編輯,迷宮求解,運算式求值

標籤雲
其他(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)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more