音視頻學習 (一) C 語言入門

前言

如今 Android 初中級開發工程師想找一份滿意的工做是愈來愈難了,固然有實力的是不愁好工做的。若是正巧你是初中級工程師想要進階音視頻方向或者對 NDK 技術感興趣的,那麼關注我準沒錯。在 5G 時代的到來,我相信音視頻方向的工程師會愈來愈吃香。那麼想要學習音視頻技術首先就得具有 C/C++ 語言基礎,下面咱們就先來學習 C 語言基礎。html

ps: 音視頻方向計劃寫一個系列文章 (初步計劃以 C/C++ 語言基礎、JNI 、Makefile/Cmake 、利用 FFmpeg 開發音視頻播放器 、RTMP 直播、OpenCV 人臉/車牌識別、OpenGL 視頻處理、視頻特效、WebRTC 音視頻通話等技術文章),該系列我也會持續更新 。C/C++ 基礎文章原本我是不打算寫的,看過個人文章都知道我寫的幾乎是系列文章,若是缺胳膊少腿的看起來也不那麼清晰流程,因此 C/C++ 基礎我就參考網上的來寫了,由於基礎這個東西,網上好的入門資料太多了,該篇就當複習參考了。有這方面基礎的能夠直接翻篇了😂。程序員

就不說那麼多廢話了,下面咱們就一塊兒來學習音視頻方向的技術,讓咱們一塊兒沉浸在學習中沒法自拔😁。算法

C 簡介

C 語言是一種通用的高級語言,最初是由丹尼斯·裏奇在貝爾實驗室爲開發 UNIX 操做系統而設計的。C 語言最開始是於 1972 年在 DEC PDP-11 計算機上被首次實現。spring

在 1978 年,布萊恩·柯林漢(Brian Kernighan)和丹尼斯·裏奇(Dennis Ritchie)製做了 C 的第一個公開可用的描述,如今被稱爲 K&R 標準。express

UNIX 操做系統,C編譯器,和幾乎全部的 UNIX 應用程序都是用 C 語言編寫的。因爲各類緣由,C 語言如今已經成爲一種普遍使用的專業語言。編程

  • 易於學習。
  • 結構化語言。
  • 它產生高效率的程序。
  • 它能夠處理底層的活動。
  • 它能夠在多種計算機平臺上編譯。

環境設置

這是隻說明在 MAC 上怎麼使用 C 語言來進行開發,環境的話須要用到 GCC 進行編譯,你能夠下載並安裝 Xcode 工具,一旦安裝上 Xcode,您就能使用 GNU 編譯器。開發工具你可使用 Xcode 或者 CLion 均可以,看我的喜愛。我這裏用的是 CLion 工具,你能夠發現 CLion 頁面跟使用風格包括快捷鍵都跟 AndroidStudio 同樣。上手極其容易。數組

C 語言入門

不知道你們在學習一門新的開發語言敲的第一行代碼是什麼?應該百分之 90 % 以上都是打印 」HelloWorld「 吧,咱們就以打印 」HelloWorld「 爲例來正式進入 C 語言的學習吧。bash

1. 程序結構

咱們先來看一下最簡單的一個 C 程序,先來打印一個 「HelloWorld」。代碼以下:數據結構

#include <stdio.h>
/** * C 語言入口程序 * @return */
int main() {//主函數,程序從這裏開始執行
    printf("C 語言入門第一行代碼 Hello World! \n");
    return 0;
}
複製代碼

能夠看到 C 語言的入口函數跟 Java 的相似吧,都是以 main 來定義的入口,接下來咱們講解一下上面這段程序的意思:dom

  1. 程序的第一行 #include <stdio.h> 是預處理器指令,告訴 C 編譯器在實際編譯以前要包含 stdio.h 文件。
  2. 下一行 /.../ 將會被編譯器忽略,這裏放置程序的註釋內容。它們被稱爲程序的註釋。
  3. 下一行 int main() 是主函數,程序從這裏開始執行。
  4. 下一行 printf(...) 是 C 中另外一個可用的函數,會在屏幕上顯示消息 "C 語言入門第一行代碼 Hello World!"。
  5. 下一行 return 0; 終止 main() 函數,並返回值 0。

固然你能夠經過命令來執行,以下所示:

1. 使用 gcc xxx.c
2. ./a.out
複製代碼

直接使用上面 2 個步驟就能夠進行執行 C 代碼了。

2. 基本語法

上一小節咱們知道了一個簡單的小應用由哪些部分組成,這將有助於咱們理解 C 語言的其它基本的構建塊。

c 程序由各類令牌組成,令牌能夠是關鍵字、標識符、常量、字串符值、或者是一個符號。

下面咱們來看一下 C 中的關鍵字,這些關鍵字不能做爲常量名,變量名或者其它標識符名稱(跟 Java 相似)。

關鍵字 說明
auto 聲明自動變量
break 跳出當前循環
case 開關語句分支
char 聲明字符型變量或者函數返回值類型
const 聲明只具可讀變量
continue 結束當前循環,開始下一個循環
default 開關語句中的其它分支
do 循環語句的循環體
double 聲明雙進度浮點型變量或者函數返回值類型
else 條件語句否認分支
enum 聲明枚舉類型
extern 聲明變量或函數是在其它文件或本文件的其餘位置定義
float 聲明浮點型變量或者函數返回值類型
for 一種循環語句
goto 無條件跳轉語句
if 條件語句
int 聲明整型變量或函數
long 聲明長整型變量或函數返回值類型
register 聲明寄存器變量
return 子程序返回語句
short 聲明短整型變量或者函數
signed 聲明有符號類型變量或者函數
sizeof 計算數據類型或者變量長度(即所佔字節數)
static 聲明靜態變量
struct 聲明結構體類型
switch 用於開關語句
typedef 用以給數據類型取別名
unsigned 聲明無符號類型變量或函數
union 聲明共用體類型
void 聲明函數無返回值或無參樹,聲明無類型指針
volatile 說明變量在程序執行中可被隱含地改變
while 循環語句的循環條件

3. 數據類型

在 C 語言中,數據類型指的是用於聲明不一樣類型的變量或函數的一個普遍的系統。變量的類型決定了變量存儲佔用的空間,以及如何解釋存儲的位模式。

C 中的類型可分爲如下幾種:

類型 說明
基本類型 它們是算術類型,包括兩種類型:整數類型和浮點類型。
枚舉類型 它們也是算術類型,被用來定義在程序中只能賦予其必定的離散整數值得變量。
void 類型 類型說明符 void 表名沒有可用的值
派生類型 它們包括:指針類型、數組類型、結構類型、共用體類型和函數類型。

整數類型

下表列出了關於標準整數類型的存儲大小和值範圍的細節

類型 32 位 64 位 值範圍
char 1 1 -128 到 127 或 0 到 255
unsigned char 1 1 0 到 255
int 4 4 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 4 4 0 到 65,535 或 0 到 4,294,967,295
short 2 2 -32,768 到 32,767
unsigned short 2 2 0 到 65,535
long 4 8 -2,147,483,648 到 2,147,483,647
unsigned long 4 8 0 到 4,294,967,295

注意: 各類類型的存儲大小與系統位數有關,但目前通用的以 64 爲系統爲主。

浮點類型

類型 比特(位)數 有效數字 取值範圍
float 4 6~7 1.2E-38 到 3.4E+38
double 8 15~16 2.3E-308 到 1.7E+308
long double 16 18~19 3.4E-4932 到 1.1E+4932

他們的字節,精度,取值範圍均可以經過代碼打印實現,以下:

void main() {
		/** * 整數類型 */

    printf("\n\n 整數類型 \n");

    //char 1 字節
    printf("char 存儲大小: %lu \n", sizeof(char));
    printf("unsinged char 存儲大小: %lu \n", sizeof(unsigned char));

    //short 2 字節
    printf("short 存儲大小: %lu \n", sizeof(short));
    printf("unsinged short 存儲大小: %lu \n", sizeof(unsigned short));

    //int 4 字節
    printf("int 存儲大小: %lu \n", sizeof(int));
    printf("unsinged int 存儲大小: %lu \n", sizeof(unsigned int));


    //long 4/8 字節
    printf("long 存儲大小: %lu \n", sizeof(long));
    printf("unsinged long 存儲大小: %lu \n", sizeof(unsigned long));

		/** * 浮點類型 */

    printf("\n\n 浮點類型 \n");
    //float 4 字節 ,精度 6 位小數
    printf("float 存儲最大字節數:%lu \n", sizeof(float));
    printf("float 最小值:%e \n", FLT_MIN);
    printf("float 最大值:%e \n", FLT_MAX);
    printf("float 精度值:%d \n", FLT_DIG);

    //double 8 字節
    printf("double 存儲最大字節數:%d \n", sizeof(double));
    printf("double 最小值:%e \n", DBL_MIN);
    printf("double 最大值:%e \n", DBL_MAX);
    printf("double 精度值:%d \n", DBL_DIG);

    //long double 16 字節
    printf("long double 存儲最大字節數:%lu byte \n", sizeof(long double));
    printf("long double 最小值:%lg \n", LDBL_MIN);
    printf("long double 最大值:%lg \n", LDBL_MAX);
    printf("long double 精度值:%d \n", LDBL_DIG);

}
複製代碼

能夠經過 sizeof 關鍵字來獲取數據類型佔用內存的大小。上面代碼能夠看到了打印中出現了不少不識的 scanf() 格式控制符,我總結了一個表,能夠參考下:

格式控制符 說明
%c 讀取一個單一的字符
%hd、%d、%ld 讀取一個十進制整數,並分別賦值給 short、int、long 類型
%ho、%o、%lo 讀取一個八進制整數(可帶前綴也可不帶),並分別賦值給 short、int、long 類型
%hx、%x、%lx 讀取一個十六進制整數(可帶前綴也可不帶),並分別賦值給 short、int、long 類型
%hu、%u、%lu 讀取一個無符號整數,並分別賦值給 unsigned short、unsigned int、unsigned long 類型
%f、%lf 讀取一個十進制形式的小數,並分別賦值給 float、double 類型
%e、%le 讀取一個指數形式的小數,並分別賦值給 float、double 類型
%g、%lg 既能夠讀取一個十進制形式的小數,也能夠讀取一個指數形式的小數,並分別賦值給 float、double 類型
%s 讀取一個字符串(以空白符爲結束)

4. 變量

變量其實只不過是程序可操做的存儲區的名稱。C 中每一個變量都有特定的類型,類型決定了變量存儲的大小和佈局,該範圍內的值均可以存儲在內存中,運算符可應用於變量上。

變量的名稱能夠由字母、數字和下劃線字符組成。它必須以字母或下劃線開頭。大寫字母和小寫字母是不一樣的,由於 C 對大小寫敏感的。

C 中的變量定義

變量定義就是告訴編譯器在何處建立變量的存儲,以及如何建立變量的存儲。變量定義指定一個數據類型,幷包含了該類型的一個或多個變量的列表,以下所示:

type list;
複製代碼

在這裏,type 必須是一個有效的 C 數據類型,能夠是 char、w_char、int、float、double 或任何用戶自定義的對象,list 能夠由一個或多個標識符名稱組成,多個標識符之間用逗號分隔。下面列出幾個有效的聲明:

int a,b,c;
char c1,c2,c3;
float f,f1,f2;
double d1,d2,d3;
複製代碼

這裏其實跟 Java 聲明變量差很少,就再也不單獨解釋了。

c 中變量聲明

變量聲明向編譯器保證變量以指定的類型和名稱存在,這樣編譯器在不須要知道變量完整細節的狀況下也能繼續進一步的編譯。變量聲明只在編譯時有它的意義,在程序鏈接時編譯器須要實際的變量聲明。

變量的聲明有兩種狀況:

  • 一、一種是須要創建存儲空間的。例如:int a 在聲明的時候就已經創建了存儲空間。
  • 二、另外一種是不須要創建存儲空間的,經過使用 extern 關鍵字聲明變量名而不定義它。 例如:extern int a 其中變量 a 能夠在別的文件中定義的。
  • 除非有 extern 關鍵字,不然都是變量的定義。
extern int i;//聲明,不是定義
int a;//聲明,也是定義
複製代碼

例子

#include <stdio.h>
//函數外定義變量 
//若是須要在一個源文件中引用另一個源文件中定義的變量,咱們只需在引用的文件中將變量加上 extern 關鍵字的聲明便可
int x;
int y;

int sum() {
    //函數內聲明變量 X , Y 爲外部變量
    x = 10;
    y = 15;
    return x + y;
}

//入口函數
void main() {
    //打印變量相加
    int result;
    result = sum();
    printf("x + y = %d",result);
}

複製代碼

輸出:

x + y = 25
複製代碼

5. 常量

常量是固定值,在程序執行期間不會改變。這些固定的值,又叫作字面量

常量能夠是任何的基本數據類型,好比整數常量、浮點常量、字符常量,或字符串字面值,也有枚舉常量。

常量就像是常規的變量,只不過常量的值在定義後不能進行修改。

在 Java 中聲明一個常量每每是在數據類型中定義 final 關鍵字就好了,可是 c 中沒有 final 關鍵字,咱們來看看怎麼定義,以下所示:

整數常量

整數常量能夠是十進制、八進制或十六進制的常量。前綴指定基數:0x 或 0X 表示十六進制,0 表示八進制,不帶前綴則默認表示十進制。

整數常量也能夠帶一個後綴,後綴是 U 和 L 的組合,U 表示無符號整數(unsigned),L 表示長整數(long)。後綴能夠是大寫,也能夠是小寫,U 和 L 的順序任意。

212         /* 合法的 */
215u        /* 合法的 */
0xFeeL      /* 合法的 */
078         /* 非法的:8 不是八進制的數字 */
032UU       /* 非法的:不能重複後綴 */
複製代碼

浮點常量

浮點常量由整數部分、小數點、小數部分和指數部分組成。您可使用小數形式或者指數形式來表示浮點常量。

當使用小數形式表示時,必須包含整數部分、小數部分,或同時包含二者。當使用指數形式表示時, 必須包含小數點、指數,或同時包含二者。帶符號的指數是用 e 或 E 引入的。

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
510E          /* 非法的:不完整的指數 */
210f          /* 非法的:沒有小數或指數 */
.e55          /* 非法的:缺乏整數或分數 */
複製代碼

定義常量

在 C 中,有兩種簡單的定義常量的方式:

  1. 使用 #define 預處理器。
  2. 使用 const 關鍵字。

下面是使用 #define 預處理器定義常量的形式:

#define identifier value
複製代碼

例子:

#define name 10L
#define age 27U
void main() {
    int  person;
    person = name + age;
    printf("values :%d",person);

}
複製代碼

const 關鍵字

您可使用 const 前綴聲明指定類型的常量,以下所示:

const type variable = value;
複製代碼

例子:

void main() {
    const int LEGTH = 10;
    const int WIDTH = 5;
    const char NEWLINE = '\n';
    int area;
    area = LEGTH * WIDTH;
    printf("value of area: %d", area);
}
複製代碼

6. 存儲類

存儲類定義 C 程序中變量/函數的範圍(可見性)和生命週期。這些說明符放置在它們所修飾的類型以前。下面列出 C 程序中可用的存儲類:

  • auto
  • register
  • static
  • extern

auto 存儲類

auto 存儲類時全部局部變量默認的存儲類。

int month;
auto int month;
複製代碼

上面定義了兩個帶有相同存儲類,auto 只能用在函數內,即 auto 只能修飾局部變量。

register 存儲類

register 存儲類用於定義存儲在寄存器中而不是 RAM 中的局部變量。這意味着變量的最大尺寸等於寄存器的大小(一般是一個詞),且不能對它應用一元的 '&' 運算符(由於它沒有內存位置)。

register int miles;
複製代碼

寄存器只用於須要快速訪問的變量,好比計數器。還應注意的是,定義 register並不意味着變量將被存儲在寄存器中,它意味着變量可能存儲在寄存器中,這取決於硬件和實現的限制。

static 存儲類

static 存儲類指示編譯器在程序的生命週期內保持局部變量的存在,而不須要在每次它進入和離開做用域時進行建立和銷燬。所以,使用 static 修飾局部變量能夠在函數調用之間保持局部變量的值。static 修飾符也能夠應用於全局變量。當 static 修飾全局變量時,會使變量的做用域限制在聲明它的文件內。

全局聲明的一個 static 變量或方法能夠被任何函數或方法調用,只要這些方法出如今跟 static 變量或方法同一個文件中。

例子:

//函數聲明
void func1(void);

static int count = 10; //全局變量 - static 默認的
void main() {
    while (count--) {
        func1();
    }
}

void func1(void) {
// 'thingy' 是 'func1' 的局部變量 - 只初始化一次
// * 每次調用函數 'func1' 'thingy' 值不會被重置。 
    static int thingy = 5;
    thingy++;
    printf("thingy 爲 %d, count 爲 %d \n", thingy, count);
}
複製代碼

輸出:

thingy 爲 6, count 爲 9 
thingy 爲 7, count 爲 8 
thingy 爲 8, count 爲 7 
thingy 爲 9, count 爲 6 
thingy 爲 10, count 爲 5 
thingy 爲 11, count 爲 4 
thingy 爲 12, count 爲 3 
thingy 爲 13, count 爲 2 
thingy 爲 14, count 爲 1 
thingy 爲 15, count 爲 0 
複製代碼

實例中 count 做爲全局變量能夠在函數內使用,thingy 在局部使用 static 修飾後,不會在每次調用時重置。

extern 存儲類

extern 存儲類用於提供一個全局變量的引用,全局變量對全部的程序文件都是可見的。當您使用 extern 時,對於沒法初始化的變量,會把變量名指向一個以前定義過的存儲位置。

當您有多個文件且定義了一個能夠在其餘文件中使用的全局變量或函數時,能夠在其餘文件中使用 extern 來獲得已定義的變量或函數的引用。能夠這麼理解,extern 是用來在另外一個文件中聲明一個全局變量或函數。

extern 修飾符一般用於當有兩個或多個文件共享相同的全局變量或函數的時候,以下所示:

第一個文件 ndk_day1.c

#include <stdio.h> //stdio.h 是一個頭文件(標準輸入輸出頭文件),#include 是一個預處理命令,用來引入頭文件。
#include "support.h" //引入本身的頭文件
int main() {
    int sum = add(2, 5);
    printf("extern 使用 :%d", sum);
}
複製代碼

聲明 support.h 頭文件

//
// Created by 陽坤 on 2019/12/13.
//

#ifndef NDK_SAMPLE_SUPPORT_H
#define NDK_SAMPLE_SUPPORT_H

#endif //NDK_SAMPLE_SUPPORT_H

extern int add(int num1,int num2);

複製代碼

頭文件的實現 support.c

int add(int num1,int num2){
    return num1 * num2;
}
複製代碼

輸出:

extern 使用 :10
複製代碼

7. 運算符

運算符是一種告訴編譯器執行特定的數學或邏輯操做的符號。C 語言內置了豐富的運算符,並提供瞭如下類型的運算符:

  • 算術運算符
  • 關係運算符
  • 邏輯運算符
  • 位運算符
  • 賦值運算符
  • 雜項運算符

算術運算符

下表顯示了 C 語言支持的全部算術運算符。假設變量 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

例子:

void main(){
    int a = 21;
    int b = 10;
    int c;

    c = a + b;
    printf("a + b = %d \n", c);

    c = a - b;
    printf("a - b = %d \n", c);

    c = a * b;
    printf("a * b = %d \n", c);

    c = a / b;
    printf("a / b = %d \n", c);

    c = a % b;
    printf("a % b = %d \n", c);

    c = ++a;
    printf("++a = %d , %d \n", c, a);

    c = b++;
    printf("b++ = %d , %d \n", c, b);

    c = b--;
    printf("b-- = %d \n", c);
}
複製代碼

輸出:

a + b = 31 
a - b = 11 
a * b = 210 
a / b = 2 
a b = 1 
++a = 22 , 22 
b++ = 10 , 11 
b-- = 11 
複製代碼

關係運算符

下表顯示了 C 語言支持的全部關係運算符。假設變量 A 的值爲 10,變量 B 的值爲 20,則:

運算符 描述 實例
== 檢查兩個操做數的值是否相等,若是相等則條件爲真。 (A == B) 爲假。
!= 檢查兩個操做數的值是否相等,若是不相等則條件爲真。 (A != B) 爲真。
> 檢查左操做數的值是否大於右操做數的值,若是是則條件爲真。 (A > B) 爲假。
< 檢查左操做數的值是否小於右操做數的值,若是是則條件爲真。 (A < B) 爲真。
>= 檢查左操做數的值是否大於或等於右操做數的值,若是是則條件爲真。 (A >= B) 爲假。
<= 檢查左操做數的值是否小於或等於右操做數的值,若是是則條件爲真。 (A <= B) 爲真。

邏輯運算符

下表顯示了 C 語言支持的全部關係邏輯運算符。假設變量 A 的值爲 1,變量 B 的值爲 0,則:

運算符 描述 實例
&& 稱爲邏輯與運算符。若是兩個操做數都非零,則條件爲真。 (A && B) 爲假。
|| 稱爲邏輯或運算符。若是兩個操做數中有任意一個非零,則條件爲真。 (A || B) 爲真。
! 稱爲邏輯非運算符。用來逆轉操做數的邏輯狀態。若是條件爲真則邏輯非運算符將使其爲假。 !(A && B) 爲真。
void main(){
   int a1 = 5;
    int b1 = 5;
    int c1;
    //若是兩個操做數都非零,則條件爲真。
    if (a1 && b1) {
        printf("a1 && b1 %d \n", true);
    } else {
        printf("a1 && b1 %d \n", false);
    }
    //若是兩個操做數中有任意一個非零,則條件爲真。
    if (a1 || b1) {
        printf("a1 || b1 %d \n", true);
    } else {
        printf("a1 || b1 %d \n", false);
    }

    //改變 a1 b1 的值
    a1 = 0;
    b1 = 10;

    //若是兩個操做數都非零,則條件爲真。
    if (a1 && b1) {
        printf("a1 && b1 %d \n", true);
    } else {
        printf("a1 && b1 %d \n", false);
    }

    if (!(a1 && b1)) {
        printf("!(a1 && b1) %d \n", true);
    } else {
        printf("a1 || b1 %d \n", false);
    }

}

複製代碼

輸出:

a1 && b1  1 
a1 || b1  1 
a1 && b1  0 
!(a1 && b1)  1 


複製代碼

位運算符

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1
void main(){
   //位運算符 & | ^ ~
    int wA = 60; //0011 1100
    int wB = 13; //0000 1101
    int wC = 10;

    //都爲真,纔是真 0000 1100
    printf("wA & wB=?%d\n", wA & wB);

    //其中一個爲真,就爲真 0011 1101
    printf("wA | wB=?%d\n", wA | wB);

    //一個爲真則爲真,2個爲真這爲假 00110001
    printf("wA ^ wB=?%d\n", wA ^ wB);

    printf("~wB=?%d\n", ~wB);


    //二進制左移運算符 左 * 4 = 40
    printf("wC<<2=?%d\n", wC << 2);

    //二進制右移運算符 右 / 4
    printf("wC>>2=?%d\n", wC >> 2);
}

複製代碼

輸出:

wA & wB=?12
wA | wB=?61
wA ^ wB=?49
~wB=?-14
wC<<2=?40
wC>>2=?2

複製代碼

下表顯示了 C 語言支持的位運算符。假設變量 A 的值爲 60,變量 B 的值爲 13,則:

運算符 描述 實例
& 按位與操做,按二進制位進行"與"運算。運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1; (A & B) 將獲得 12,即爲 0000 1100
| 按位或運算符,按二進制位進行"或"運算。運算規則:0|0=0; 0|1=1; 1|0=1; 1|1=1; (A | B) 將獲得 61,即爲 0011 1101
^ 異或運算符,按二進制位進行"異或"運算。運算規則:0^0=0; 0^1=1; 1^0=1; 1^1=0; (A ^ B) 將獲得 49,即爲 0011 0001
~ 取反運算符,按二進制位進行"取反"運算。運算規則:~1=0; ~0=1; (~A ) 將獲得 -61,即爲 1100 0011,一個有符號二進制數的補碼形式。
<< 二進制左移運算符。將一個運算對象的各二進制位所有左移若干位(左邊的二進制位丟棄,右邊補0)。 A << 2 將獲得 240,即爲 1111 0000
>> 二進制右移運算符。將一個數的各二進制位所有右移若干位,正數左補0,負數左補1,右邊丟棄。 A >> 2 將獲得 15,即爲 0000 1111

賦值運算符

下表列出了 C 語言支持的賦值運算符:

運算符 描述 實例
= 簡單的賦值運算符,把右邊操做數的值賦給左邊操做數 C = A + B 將把 A + B 的值賦給 C
+= 加且賦值運算符,把右邊操做數加上左邊操做數的結果賦值給左邊操做數 C += A 至關於 C = C + A
-= 減且賦值運算符,把左邊操做數減去右邊操做數的結果賦值給左邊操做數 C -= A 至關於 C = C - A
*= 乘且賦值運算符,把右邊操做數乘以左邊操做數的結果賦值給左邊操做數 C *= A 至關於 C = C * A
/= 除且賦值運算符,把左邊操做數除以右邊操做數的結果賦值給左邊操做數 C /= A 至關於 C = C / A
%= 求模且賦值運算符,求兩個操做數的模賦值給左邊操做數 C %= A 至關於 C = C % A
<<= 左移且賦值運算符 C <<= 2 等同於 C = C << 2
>>= 右移且賦值運算符 C >>= 2 等同於 C = C >> 2
&= 按位與且賦值運算符 C &= 2 等同於 C = C & 2
^= 按位異或且賦值運算符 C ^= 2 等同於 C = C ^ 2
|= 按位或且賦值運算符 C |= 2 等同於 C = C | 2

例子:

void main(){
int wAA = 21;
    int wBB;

    wBB = wAA;
    printf("= %d\n", wBB);

    wBB += wAA;
    printf("+= %d\n", wBB);

    wBB -= wAA;
    printf("-= %d\n", wBB);

    wBB *= wAA;
    printf("*= %d\n", wBB);

    wBB /= wAA;
    printf("/= %d\n", wBB);

    wBB %= wAA;
    printf("%= %d\n", wBB);

    wBB <<= wAA;
    printf("<<= %d\n", wBB);

    wBB <<= wAA;
    printf(">>= %d\n", wBB);

    wBB &= wAA;
    printf("&= %d\n", wBB);

    wBB ^= wAA;
    printf("^= %d\n", wBB);

    wBB |= wAA;
    printf("|= %d\n", wBB);
}

複製代碼

輸出:

= 21
+= 42
-= 21
*= 441
/= 21
= 0
<<= 0
>>= 0
&= 0
^= 21
|= 21

複製代碼

雜項運算符 sizeof、&、三元

下表列出了 C 語言支持的其餘一些重要的運算符,包括 sizeof? :

運算符 描述 實例
sizeof() 返回變量的大小。 sizeof(a) 將返回 4,其中 a 是整數。
& 返回變量的地址。 &a; 將給出變量的實際地址。
* 指向一個變量。 *a; 將指向一個變量。
? : 條件表達式 若是條件爲真 ? 則值爲 X : 不然值爲 Y

例子:

void main(){
  	int zxA = 4;
    short zxB;
    double zxC;
    int *ptr;

    //sizeOf 運算符實例 ,lu 32位無符號整數
    printf("zxA sizeOf = %lu \n", sizeof(zxA));
    printf("zxB sizeOf = %lu \n", sizeof(zxB));
    printf("zxC sizeOf = %lu \n", sizeof(zxC));

    //& 和 * 運算符實例
    ptr = &zxA; //將 zxA 的地址值複製給 ptr 指針
    printf("zxA 的值爲:%d \n", zxA);
    printf("*ptr 的值爲:%d \n", *ptr);
  
  	//三元運算符
    zxA = 10;
    zxB = (zxA == 1) ? 20 : 30;
    printf("zxb 的值爲:%d \n", zxB);

    zxB = (zxA == 10) ? 20 : 30;
    printf("zxb 的值爲:%d \n", zxB);
}

複製代碼

輸出:

zxA sizeOf = 4 
zxB sizeOf = 2 
zxC sizeOf = 8 
zxA 的值爲:4 
*ptr 的值爲:4 
zxb 的值爲:30 
zxb 的值爲:20 

複製代碼

8. 判斷

C 語言把任何非零非空的值假定爲 true,把null 假定爲 false

C 語言提供瞭如下類型的判斷語句。點擊連接查看每一個語句的細節。

語句 描述
if 語句 一個 if 語句 由一個布爾表達式後跟一個或多個語句組成。
if...else 語句 一個 if 語句 後可跟一個可選的 else 語句,else 語句在布爾表達式爲假時執行。
嵌套 if 語句 您能夠在一個 ifelse if 語句內使用另外一個 ifelse if 語句。
switch 語句 一個 switch 語句容許測試一個變量等於多個值時的狀況。
嵌套 switch 語句 您能夠在一個 switch 語句內使用另外一個 switch 語句。

?:運算符

跟 Java 同樣

void main(){
 		int pdNumber;
    printf("輸入一個數字:");
    scanf("%d", &pdNumber);
    (pdNumber % 2 == 0) ? printf("偶數") : printf("基數");
}

複製代碼

9. 循環

C 語言提供瞭如下幾種循環類型。點擊連接查看每一個類型的細節。

循環類型 描述
while 循環 當給定條件爲真時,重複語句或語句組。它會在執行循環主體以前測試條件。
for 循環 屢次執行一個語句序列,簡化管理循環變量的代碼。
do...while 循環 除了它是在循環主體結尾測試條件外,其餘與 while 語句相似。
嵌套循環 您能夠在 while、for 或 do..while 循環內使用一個或多個循環。

循環控制語句

循環控制語句改變你代碼的執行順序。經過它你能夠實現代碼的跳轉。

C 提供了下列的循環控制語句。點擊連接查看每一個語句的細節。

控制語句 描述
break 語句 終止循環switch 語句,程序流將繼續執行緊接着循環或 switch 的下一條語句。
continue 語句 告訴一個循環體馬上中止本次循環迭代,從新開始下次循環迭代。
goto 語句 將控制轉移到被標記的語句。可是不建議在程序中使用 goto 語句。

使用方法能夠參考 Java ,下面給出循環的例子:

void main(){
      //限制
    for (int i = 0; i < 6; i++) {
        printf("限制循環,%d \n",i);
    }
    //無限循環
    for (;;) {
        printf("該循環會一直執行下去!\n");
    }
}

複製代碼

10. 函數

函數定義

C 語言中的函數定義的通常形式以下:

return_type function_name( parameter list ) {
   body of the function
}

複製代碼

在 C 語言中,函數由一個函數頭和一個函數主體組成。下面列出一個函數的全部組成部分:

  • **返回類型:**一個函數能夠返回一個值。return_type 是函數返回的值的數據類型。有些函數執行所需的操做而不返回值,在這種狀況下,return_type 是關鍵字 void
  • **函數名稱:**這是函數的實際名稱。函數名和參數列表一塊兒構成了函數簽名。
  • **參數:**參數就像是佔位符。當函數被調用時,您向參數傳遞一個值,這個值被稱爲實際參數。參數列表包括函數參數的類型、順序、數量。參數是可選的,也就是說,函數可能不包含參數。
  • **函數主體:**函數主體包含一組定義函數執行任務的語句。

例子:

/* 函數返回兩個數中較大的那個數 */
int max(int num1, int num2) {
   /* 局部變量聲明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

複製代碼

函數聲明

函數聲明會告訴編譯器函數名稱及如何調用函數。函數的實際主體能夠單獨定義。

函數聲明包括如下幾個部分:

return_type function_name( parameter list );

複製代碼

針對上面定義的函數 max(),如下是函數聲明:

int max(int num1, int num2);

複製代碼

在函數聲明中,參數的名稱並不重要,只有參數的類型是必需的,所以下面也是有效的聲明:

int max(int, int);

複製代碼

當您在一個源文件中定義函數且在另外一個文件中調用函數時,函數聲明是必需的。在這種狀況下,您應該在調用函數的文件頂部聲明函數。

調用函數

//函數聲明
int max(int num1, int num2);

/** *C 函數 */
void main() {
    //找出函數中最大值
    printf("找出函數中最大值,%d \n",max(66,88));

}

int max(int num1, int num2) {
    return (num1 > num2) ? num1 : num2;
}

複製代碼

輸出:

找出函數中最大值,88 

複製代碼

函數參數

若是函數要使用參數,則必須聲明接受參數值的變量。這些變量稱爲函數的形式參數

形式參數就像函數內的其餘局部變量,在進入函數時被建立,退出函數時被銷燬。

調用類型 描述
傳值調用 該方法把參數的實際值複製給函數的形式參數。在這種狀況下,修改函數內的形式參數不會影響實際參數。
引用調用 經過指針傳遞方式,形參爲指向實參地址的指針,當對形參的指向操做時,就至關於對實參自己進行的操做。

11. 做用域規則

任何一種編程中,做用域是程序中定義的變量所存在的區域,超過該區域變量就不能被訪問。C 語言中有三個地方能夠聲明變量:

  1. 在函數或塊內部的局部變量
  2. 在全部函數外部的全局變量
  3. 形式參數的函數參數定義中

讓咱們來看看什麼是局部變量、全局變量和形式參數。

局部變量

在某個函數或塊的內部聲明的變量稱爲局部變量。它們只能被該函數或該代碼塊內部的語句使用。局部變量在函數外部是不可知的。下面是使用局部變量的實例。在這裏,全部的變量 a、b 和 c 是 main() 函數的局部變量。

void main(){
    //局部變量
    int a, b;
    int c;
    //初始化局部變量
    a = 10;
    b = 20;
    c = a + b;
    //%d:以十進制形式輸出帶符號整數(正數不輸出符號)
    printf("values of a = %d,b = %d and c = %d \n", a, b, c);
}

複製代碼

輸出:

values of a = 10,b = 20 and c = 30 

複製代碼

全局變量

全局變量是定義在函數外部,一般是在程序的頂部。全局變量在整個程序生命週期內都是有效的,在任意的函數內部能訪問全局變量。

全局變量能夠被任何函數訪問。也就是說,全局變量在聲明後整個程序中都是可用的。下面是使用全局變量和局部變量的實例:

//全局變量聲明
int g;
void main(){
   int a, b;
    //初始化局部變量
    a = 10;
    b = 20;
   //所有變量賦值
    g = a + c;
    printf("values of a = %d,bc = %d and g = %d \n", a, c, g);
}

複製代碼

輸出:

values of a = 10,bc = 30 and g = 40 

複製代碼

形式參數

函數的參數,形式參數,被看成該函數內的局部變量,若是與全局變量同名它們會優先使用。下面是一個實例:

int sumA(int a, int b) {
    printf("value of a in sum() = %d\n", a);
    printf("value of b in sum() = %d\n", b);
    return x + y;
}

void main(){
  int a, b,c;
    //初始化局部變量
    a = 10;
    b = 20;
  c = sumA(a, b);
  printf("value of c in main() = %d\n", c);
}

複製代碼

輸出:

value of a in main() = 30

複製代碼

全局變量和局部變量的區別

  • 全局變量保存在內存的全局存儲區中,佔用靜態的存儲單元;
  • 局部變量保存在棧中,只有在所在函數被調用時才動態地爲變量分配存儲單元。

初始化局部變量和全局變量的默認值

數據類型 初始化默認值
int 0
char '\0'
float 0
double 0
pointer NULL

12. 數組

C 語言支持數組數據結構,它能夠存儲一個固定大小的相同類型元素的順序集合。數組是用來存儲一系列數據,但它每每被認爲是一系列相同類型的變量。

數組的聲明並非聲明一個個單獨的變量,好比 number0、number一、...、number99,而是聲明一個數組變量,好比 numbers,而後使用 numbers[0]、numbers[1]、...、numbers[99] 來表明一個個單獨的變量。數組中的特定元素能夠經過索引訪問。

全部的數組都是由連續的內存位置組成。最低的地址對應第一個元素,最高的地址對應最後一個元素。

聲明數組

在 C 中要聲明一個數組,須要指定元素的類型和元素的數量,以下所示:

type arrayName [ arraySize ];

複製代碼

這叫作一維數組。arraySize 必須是一個大於零的整數常量,type 能夠是任意有效的 C 數據類型。例如,要聲明一個類型爲 double 的包含 10 個元素的數組 balance,聲明語句以下:

double balance[10];

複製代碼

初始化數組

void main(){
  double balance[10] = {1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0}
}

複製代碼

大括號 { } 之間的值的數目不能大於咱們在數組聲明時在方括號 [ ] 中指定的元素數目。

若是您省略掉了數組的大小,數組的大小則爲初始化時元素的個數。所以,若是:

void main(){
  double balance[] = {1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0}
}

複製代碼

您將建立一個數組,它與前一個實例中所建立的數組是徹底相同的。下面是一個爲數組中某個元素賦值的實例:

balance[1] = 50.5;

複製代碼

訪問數組元素

//跟 Java 同樣
double value = balance[1]

複製代碼

例子:

void main() {
    //定義一個長度爲 10 的整數數組
    int n[10];
    int i, j;

    //初始化數組元素
    for (i = 0; i < 10; i++) {
        n[i] = 2 * i;
    }

    //輸出元素中的數據
    for (int k = 0; k < 10; ++k) {
        printf("Element[%d] = %d \n", k, n[k]);
    }

    //總的大小除以其中一個大小就獲得了 數組長度
    printf("整數數組 n 的長度: %d \n", sizeof(n) / sizeof(n[0]));

    //輸出元素中的數據
    for (int k = 0; k < sizeof(n) / sizeof(n[0]); ++k) {
        printf("Element[%d] = %d \n", k, n[k]);
    }
}

複製代碼

輸出:

Element[0] = 0 
Element[1] = 2 
Element[2] = 4 
Element[3] = 6 
Element[4] = 8 
Element[5] = 10 
Element[6] = 12 
Element[7] = 14 
Element[8] = 16 
Element[9] = 18 
整數數組 n 的長度: 10 
Element[0] = 0 
Element[1] = 2 
Element[2] = 4 
Element[3] = 6 
Element[4] = 8 
Element[5] = 10 
Element[6] = 12 
Element[7] = 14 
Element[8] = 16 
Element[9] = 18 


複製代碼

C 中數組詳解

在 C 中,數組是很是重要的,咱們須要瞭解更多有關數組的細節。下面列出了 C 程序員必須清楚的一些與數組相關的重要概念:

概念 描述
多維數組 C 支持多維數組。多維數組最簡單的形式是二維數組。
傳遞數組給函數 您能夠經過指定不帶索引的數組名稱來給函數傳遞一個指向數組的指針。
從函數返回數組 C 容許從函數返回數組。
指向數組的指針 您能夠經過指定不帶索引的數組名稱來生成一個指向數組中第一個元素的指針。

13. 枚舉

枚舉是 C 語言中的一種基本數據類型,它可讓數據更簡潔,更易讀。

枚舉語法定義格式爲:

enum&emsp;枚舉名&emsp;{枚舉元素1,枚舉元素2,……};

複製代碼

接下來咱們舉個例子,好比:一星期有 7 天,若是不用枚舉,咱們須要使用 #define 來爲每一個整數定義一個別名:

#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7

複製代碼

這個看起來代碼量就比較多,接下來咱們看看使用枚舉的方式:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

複製代碼

這樣看起來是否是更簡潔了。

**注意:**第一個枚舉成員的默認值爲整型的 0,後續枚舉成員的值在前一個成員上加 1。咱們在這個實例中把第一個枚舉成員的值定義爲 1,第二個就爲 2,以此類推。

能夠在定義枚舉類型時改變枚舉元素的值:

enum season {spring, summer=3, autumn, winter};

複製代碼

沒有指定值的枚舉元素,其值爲前一元素加 1。也就說 spring 的值爲 0,summer 的值爲 3,autumn 的值爲 4,winter 的值爲 5

枚舉變量的定義

前面咱們只是聲明瞭枚舉類型,接下來咱們看看如何定義枚舉變量。

咱們能夠經過如下三種方式來定義枚舉變量

一、先定義枚舉類型,再定義枚舉變量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

複製代碼

二、定義枚舉類型的同時定義枚舉變量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

複製代碼

三、省略枚舉名稱,直接定義枚舉變量

enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

複製代碼

例子:

void main() {
    //遍歷一週
    for (day = MON; day <= SUN; day++) {
        printf("周: %d \n", day);
    }

    enum color { red=1, green, blue ,black};

    enum  color favorite_color;

// ask user to choose color
    printf("請輸入你喜歡的顏色: (1. red, 2. green, 3. blue): ");
    scanf("%d", &favorite_color);

// 輸出結果
    switch (favorite_color)
    {
        case red:
            printf("你喜歡的顏色是紅色");
            break;
        case green:
            printf("你喜歡的顏色是綠色");
            break;
        case blue:
            printf("你喜歡的顏色是藍色");
            break;
        case black:
            printf("你喜歡的顏色是黑色");
            break;
        default:
            printf("你沒有選擇你喜歡的顏色");
    }


    //將整數轉換爲枚舉
    enum day
    {
        saturday,
        sunday,
        monday,
        tuesday,
        wednesday,
        thursday,
        friday
    } ;
    int a = 1;
    enum day weekend;
    weekend = (enum day)a;
    printf("weekend:%d \n",weekend);
}

複製代碼

輸出:

周: 1 
周: 2 
周: 3 
周: 4 
周: 5 
周: 6 
周: 7 
請輸入你喜歡的顏色: (1. red, 2. green, 3. blue): 1
你喜歡的顏色是紅色weekend:1 

複製代碼

14. 指針

學習 C 語言的指針既簡單又有趣。經過指針,能夠簡化一些 C 編程任務的執行,還有一些任務,如動態內存分配,沒有指針是沒法執行的。因此,想要成爲一名優秀的 C 程序員,學習指針是頗有必要的。

正如您所知道的,每個變量都有一個內存位置,每個內存位置都定義了可以使用連字號(&)運算符訪問的地址,它表示了在內存中的一個地址。請看下面的實例,它將輸出定義的變量地址:

void main(){
    int var1;
    char var2[10];
    //%p : 輸出指針地址
    printf("var1 變量的地址:%p \n", &var1);
    printf("var2 變量的地址:%p \n", &var2);
}

複製代碼

輸出:

var1 變量的地址:0x7ffee7e976b8 
var2 變量的地址:0x7ffee7e976be 

複製代碼

經過上面的實例,咱們瞭解了什麼是內存地址以及如何訪問它。接下來讓咱們看看什麼是指針。

什麼是指針?

指針是一個變量,其值爲另外一個變量的地址,即內存位置的直接地址。就像其餘變量或常量同樣,您必須在使用指針存儲其餘變量地址以前,對其進行聲明。指針變量聲明的通常形式爲:

type *var-name

複製代碼

在這裏,type 是指針的基類型,它必須是一個有效的 C 數據類型,var-name 是指針變量的名稱。用來聲明指針的星號 * 與乘法中使用的星號是相同的。可是,在這個語句中,星號是用來指定一個變量是指針。如下是有效的指針聲明:

int *i; //一個整型的指針 
double *d;//double 型指針
float *f;//浮點型指針
char *ch//字符型指針

複製代碼

全部實際數據類型,無論是整型、浮點型、字符型,仍是其餘的數據類型,對應指針的值的類型都是同樣的,都是一個表明內存地址的長的十六進制數。

不一樣數據類型的指針之間惟一的不一樣是,指針所指向的變量或常量的數據類型不一樣。

如何使用指針?

使用指針時會頻繁進行如下幾個操做:定義一個指針變量、把變量地址賦值給指針、訪問指針變量中可用地址的值。這些是經過使用一元運算符 ***** 來返回位於操做數所指定地址的變量的值。下面的實例涉及到了這些操做:

例子:

//如何使用指針
    int var = 66;//實際變量的聲明
    int *ip;//指針變量的聲明

    ip = &var; //指針變量中存儲 var 的地址
    printf("var 的地址 : %p \n", var);

    //在指針變量中存儲的地址
    printf("ip 的地址:%p \n", ip);

    //使用指針訪問地址
    printf("ip 指針對應的地址:%p \n", *ip);

    //使用指針訪問地址對應的值
    printf("ip 指針對應的地址:%d \n", *ip);

複製代碼

輸出:

var 的地址 : 0x42  
ip 的地址:0x7ffee96eb6b4  
ip 指針對應的地址:0x42 
ip 指針對應的地址:66 

複製代碼

C 中的 NULL 指針

在變量聲明的時候,若是沒有確切的地址能夠賦值,爲指針變量賦一個 NULL 值是一個良好的編程習慣。賦爲 NULL 值的指針被稱爲指針。

NULL 指針是一個定義在標準庫中的值爲零的常量。請看下面的程序:

void main(){
    //賦值一個 NULL 指針
    int *ptr = NULL;
    printf("ptr 的地址是: %p \n", ptr);

    //檢查一個空指針
    if (ptr) printf("若是 ptr 不是空指針,則執行"); else printf("若是 ptr 是空指針,則執行");
}

複製代碼

輸出:

ptr 的地址是: 0x0 ptr 是空指針

C 指針詳解

在 C 中,有不少指針相關的概念,這些概念都很簡單,可是都很重要。下面列出了 C 程序員必須清楚的一些與指針相關的重要概念:

概念 描述
指針的算術運算 能夠對指針進行四種算術運算:++、--、+、-
指針數組 能夠定義用來存儲指針的數組。
指向指針的指針 C 容許指向指針的指針。
傳遞指針給函數 經過引用或地址傳遞參數,使傳遞的參數在調用函數中被改變。
從函數返回指針 C 容許函數返回指針到局部變量、靜態變量和動態內存分配。

15. 函數指針與回調函數

函數指針是指向函數的指針變量。

一般咱們說的指針變量是指向一個整型、字符型或數組等變量,而函數指針是指向函數。

函數指針能夠像通常函數同樣,用於調用函數、傳遞參數。

函數指針變量的聲明:

typedef int (*fun_ptr)(int,int)//聲明一個指向一樣參數,返回值得函數指針類型 複製代碼

例子:

int max(int num1, int num2) {
    return (num1 > num2) ? num1 : num2;
}

void main() {

    //定義一個返回值爲 int 類型,參數爲 (int,int) 形式的函數指針
    int (*p)(int, int) = *max;
    int a, b, c, d;
    printf("請輸入三個數字:\n");
    scanf("%d %d %d", &a, &b, &c);

    //與直接調用函數等價,d = max(max(a,b),c);
    d = p(p(a, b), c);
    printf("最大數字是: %d \n", d);

}

複製代碼

回調函數

函數指針變量能夠做爲某個函數的參數來使用的,回調函數就是一個經過函數指針調用的函數。

簡單講:回調函數是由別人的函數執行時調用你實現的函數。

例子:

例子中 populate_array 函數定義了三個參數,其中第三個參數是函數的指針,經過該函數來設置數組的值。

實例中咱們定義了回調函數 getNextRandomValue,它返回一個隨機值,它做爲一個函數指針傳遞給 populate_array 函數。

populate_array 將調用 10 次回調函數,並將回調函數的返回值賦值給數組。

#include <stdlib.h>  
#include <stdio.h>
//回調函數
void populate_array(int *array, size_t arraySize, int(*getNextValue)(void)) {
    printf("array 地址:%p \n", array);
    for (size_t i = 0; i < arraySize; i++) {
        array[i] = getNextValue();
        printf(" array[%d] ,存儲值:%d \n", i, array[i]);
    }
}

//獲取一個隨機數
int getNextRandomValue(void) {
    return rand();
}

void main() {

    //回調函數
    int array[10];
    printf("Int array 地址:%p \n", array);
    populate_array(array, sizeof(array)/sizeof(array[0]), getNextRandomValue);
    for (int i = 0; i < sizeof(array)/sizeof(array[0]); ++i) {
        printf(" array[%d] , 對應值爲:%d \n", i, array[i]);
    }

}

複製代碼

輸出:

Int array 地址:0x7ffeebf1a650 
array 地址:0x7ffeebf1a650 
 array[0] ,存儲值:16807 
 array[1] ,存儲值:282475249 
 array[2] ,存儲值:1622650073 
 array[3] ,存儲值:984943658 
 array[4] ,存儲值:1144108930 
 array[5] ,存儲值:470211272 
 array[6] ,存儲值:101027544 
 array[7] ,存儲值:1457850878 
 array[8] ,存儲值:1458777923 
 array[9] ,存儲值:2007237709 
 array[0] , 對應值爲:16807 
 array[1] , 對應值爲:282475249 
 array[2] , 對應值爲:1622650073 
 array[3] , 對應值爲:984943658 
 array[4] , 對應值爲:1144108930 
 array[5] , 對應值爲:470211272 
 array[6] , 對應值爲:101027544 
 array[7] , 對應值爲:1457850878 
 array[8] , 對應值爲:1458777923 
 array[9] , 對應值爲:2007237709 

複製代碼

16. 字符串

在 C 語言中,字符串其實是使用 null 字符 '\0' 終止的一維字符數組。所以,一個以 null 結尾的字符串,包含了組成字符串的字符。

下面的聲明和初始化建立了一個 "Hello" 字符串。因爲在數組的末尾存儲了空字符,因此字符數組的大小比單詞 "Hello" 的字符數多一個。

char ch[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

複製代碼

也可使用如下簡寫模式:

char ch[6] = "Hello"

複製代碼

字符串在 C/C++ 中內存表示:

其實,您不須要把 null 字符放在字符串常量的末尾。C 編譯器會在初始化數組時,自動把 '\0' 放在字符串的末尾。讓咱們嘗試輸出上面的字符串:

void main(){
      //定義一個 char 數組
    char string[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
    //簡寫
    char string2[6] = "Hello";
    //%s:輸出字符串
    printf("string message : %s\n", string);
}

複製代碼

輸出:

string message : Hello

複製代碼

C 中對字符串操做的 API

序號 函數 & 目的
1 strcpy(s1, s2); 複製字符串 s2 到字符串 s1。
2 strcat(s1, s2); 鏈接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的長度。
4 strcmp(s1, s2); 若是 s1 和 s2 是相同的,則返回 0;若是 s1<s2 則返回小於 0;若是 s1>s2 則返回大於 0。
5 strchr(s1, ch); 返回一個指針,指向字符串 s1 中字符 ch 的第一次出現的位置。
6 strstr(s1, s2); 返回一個指針,指向字符串 s1 中字符串 s2 的第一次出現的位置。

例子:

void main(){
      //字符串操做
    char str1[12] = "Hello";
    char str2[12] = "World";
    char str3[12];
    int len;

    //將 str1 複製到 str3
    strcpy(str3, str1);
    printf("strcpy (str3,str1) :%s\n", str3);

    //拼接字符串 str1 + str2
    strcat(str1, str2);
    printf("strcat(str1,str2) :%s\n", str1);

    //返回字符串的長度
    len = strlen(str1);
    printf("strlen(str1) :%d\n", len);
}

複製代碼

輸出:

strcpy (str3,str1) :Hello
strcat(str1,str2) :HelloWorld
strlen(str1) :10

複製代碼

17. 結構體

C 數組容許定義可存儲相同類型數據項的變量,結構是 C 編程中另外一種用戶自定義的可用的數據類型,它容許您存儲不一樣類型的數據項。

結構用於表示一條記錄,假設您想要跟蹤圖書館中書本的動態,您可能須要跟蹤每本書的下列屬性:

  • Title
  • Author
  • Subject
  • Book ID

定義結構

爲了定義結構,您必須使用 struct 語句。struct 語句定義了一個包含多個成員的新的數據類型,struct 語句的格式以下:

struct name{
  member-list;
  member-list;
  ...
}name_tag,

複製代碼

name 是結構的標籤。

member-list 是標準的變量定義,好比 int i;或者 float f,或者其它有效的變量定義。

name_tag 結構變量,定義在結構的末尾,最後一個分號以前,你能夠指定一個或多個結構變量,下面是聲明 Book 的結構方式:

struct Books{
  char title[50];
  char author[50];
  char subject[100];
  int book_id;
} book;

複製代碼

注意:在定義結構體的時候name、member-list、name_tag 這 3 部分至少要出現 2 個。

結構體變量的初始化

和其它類型變量同樣,在初始化的時候能夠指定初始值。

//定義一個 Books 結構,相似於 Java 中的數據 bean
struct Books {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
    double rmb;
} book = {"Java", "Android", "C 語言", 666, 55.5};


void main(){
      //打印 Books
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\nrmb: %f\n", book.title,
           book.author, book.subject, book.book_id, book.rmb);
}

複製代碼

輸出:

title : Java
author: Android
subject: C 語言
book_id: 666
rmb: 55.500000

複製代碼

訪問結構成員

struct Books2 {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};
void main(){
      //訪問 Books2 結構成員
    struct Books2 Books2A;//聲明 Books2A 類型爲 Books2
    struct Books2 Books2B;//聲明 Books2B 類型爲 Books2

    //Books2A 詳述
    strcpy(Books2A.title, "C Plus");
    strcpy(Books2A.author, "Nuha Ali");
    strcpy(Books2A.subject, "C");
    Books2A.book_id = 666888;

    //Books2B 詳述
    strcpy(Books2B.title, "C++ Plus");
    strcpy(Books2B.author, "DevYK");
    strcpy(Books2B.subject, "C++");
    Books2B.book_id = 666999;

    // 輸出 Book1 信息
    printf("Book 1 title : %s\n", Books2A.title);
    printf("Book 1 author : %s\n", Books2A.author);
    printf("Book 1 subject : %s\n", Books2A.subject);
    printf("Book 1 book_id : %d\n", Books2A.book_id);

    // 輸出 Book2 信息
    printf("Book 2 title : %s\n", Books2B.title);
    printf("Book 2 author : %s\n", Books2B.author);
    printf("Book 2 subject : %s\n", Books2B.subject);
    printf("Book 2 book_id : %d\n", Books2B.book_id);
}

複製代碼

輸出:

Book 1 title : C Plus
Book 1 author : Nuha Ali
Book 1 subject : C
Book 1 book_id : 666888
Book 2 title : C++ Plus
Book 2 author : DevYK
Book 2 subject : C++
Book 2 book_id : 666999

複製代碼

結構做爲函數參數

//函數聲明
void printBook(struct Books2 books2);

void main(){
      //訪問 Books2 結構成員
    struct Books2 Books2A;//聲明 Books2A 類型爲 Books2
    struct Books2 Books2B;//聲明 Books2B 類型爲 Books2

    //Books2A 詳述 ,將 CPlus copy 到 title 中
    strcpy(Books2A.title, "C Plus");
    strcpy(Books2A.author, "Nuha Ali");
    strcpy(Books2A.subject, "C");
    Books2A.book_id = 666888;

    //Books2B 詳述
    strcpy(Books2B.title, "C++ Plus");
    strcpy(Books2B.author, "DevYK");
    strcpy(Books2B.subject, "C++");
    Books2B.book_id = 666999;

    // 輸出 Book1 信息
    printf("Book 1 title : %s\n", Books2A.title);
    printf("Book 1 author : %s\n", Books2A.author);
    printf("Book 1 subject : %s\n", Books2A.subject);
    printf("Book 1 book_id : %d\n", Books2A.book_id);

    // 輸出 Book2 信息
    printf("Book 2 title : %s\n", Books2B.title);
    printf("Book 2 author : %s\n", Books2B.author);
    printf("Book 2 subject : %s\n", Books2B.subject);
    printf("Book 2 book_id : %d\n", Books2B.book_id);

    printf("\n\n\n");
    //結構做爲函數參數
    printBook(Books2A);
    printBook(Books2B);
}


void printBook(struct Books2 book) {
    printf("Book title : %s\n", book.title);
    printf("Book author : %s\n", book.author);
    printf("Book subject : %s\n", book.subject);
    printf("Book book_id : %d\n", book.book_id);
}

複製代碼

輸出:

Book 1 title : C Plus
Book 1 author : Nuha Ali
Book 1 subject : C
Book 1 book_id : 666888
Book 2 title : C++ Plus
Book 2 author : DevYK
Book 2 subject : C++
Book 2 book_id : 666999



Book  title : C Plus
Book  author : Nuha Ali
Book  subject : C
Book  book_id : 666888
Book  title : C++ Plus
Book  author : DevYK
Book  subject : C++
Book  book_id : 666999




複製代碼

指向結構的指針

您能夠定義指向結構的指針,方式與定義指向其餘類型變量的指針類似,以下所示:

struct Books *struct_pointer;

複製代碼

如今,您能夠在上述定義的指針變量中存儲結構變量的地址。爲了查找結構變量的地址,請把 & 運算符放在結構名稱的前面,以下所示:

struct_pointer = &Book1;

複製代碼

爲了使用指向該結構的指針訪問結構的成員,您必須使用 -> 運算符,以下所示:

struct_pointer->title;

複製代碼

例子:

//定義指向結構的指針
void printBookZZ(struct Books2 *books2);

void main(){
      //訪問 Books2 結構成員
    struct Books2 Books2A;//聲明 Books2A 類型爲 Books2
    struct Books2 Books2B;//聲明 Books2B 類型爲 Books2

    //Books2A 詳述 ,將 CPlus copy 到 title 中
    strcpy(Books2A.title, "C Plus");
    strcpy(Books2A.author, "Nuha Ali");
    strcpy(Books2A.subject, "C");
    Books2A.book_id = 666888;

    //Books2B 詳述
    strcpy(Books2B.title, "C++ Plus");
    strcpy(Books2B.author, "DevYK");
    strcpy(Books2B.subject, "C++");
    Books2B.book_id = 666999;
  
     //經過內存地址傳遞信息,爲了查找結構變量的地址,請把 & 運算符放在結構名稱的前面
     printBookZZ(&Books2A);
     printBookZZ(&Books2B);
} 

/** * 爲了使用指向該結構的指針訪問結構的成員,您必須使用 -> 運算符,以下所示: * @param book */
void printBookZZ(struct Books2 *book) {
    printf("Book -> title : %s\n", book->title);
    printf("Book -> author : %s\n", book->author);
    printf("Book -> subject : %s\n", book->subject);
    printf("Book -> book_id : %d\n", book->book_id);
}

複製代碼

位域

有些信息在存儲時,並不須要佔用一個完整的字節,而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有 0 和 1 兩種狀態,用 1 位二進位便可。爲了節省存儲空間,並使處理簡便,C 語言又提供了一種數據結構,稱爲"位域"或"位段"。

所謂"位域"是把一個字節中的二進位劃分爲幾個不一樣的區域,並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做。這樣就能夠把幾個不一樣的對象用一個字節的二進制位域來表示。

典型的實例:

  • 用 1 位二進位存放一個開關量時,只有 0 和 1 兩種狀態。
  • 讀取外部文件格式——能夠讀取非標準的文件格式。

位域ed 定義:

struct 位域結構名稱{
  位域列表
};

複製代碼

位域列表的形式爲:

類型說明符 位域名:位域長度

複製代碼

例如:

struct bean {
  int a:8;
  int b:4;
  int c:4;
}data;

複製代碼

說明 data 爲 bean 變量,共佔 2個字節。其中位域 a 佔 8 位,位域 b 佔 4 位,位域 c 佔 4 位。

注意:

  • 一個位域存儲在同一個字節中,如一個字節所剩空間不夠存放另外一位域時,則會從下一單元起存放該位域。也能夠有意使某位域從下一單元開始。例如:
struct bean{
  unsigned a:4;
  unsigned  :4;//空域
  unsigned b:4;//從下一個單元開始存放
  unsigned c:4;
}

複製代碼

在這個位域定義中共佔用 2 個字節,a 佔第一字節的 4 位,後 4 位填 0 表示不使用,b 從第二字節開始,佔用 4 位,c 佔用 4 位。

  • 因爲位域不容許跨兩個字節,所以位域的長度不能大於一個字節的長度,也就是說不能超過8位二進位。若是最大長度大於計算機的整數字長,一些編譯器可能會容許域的內存重疊,另一些編譯器可能會把大於一個域的部分存儲在下一個字中。
  • 位域能夠是無名位域,這時它只用來做填充或調整位置。無名的位域是不能使用的。例如:
struct k{
 int a:1;
 int  :2;    /* 該 2 位不能使用 */
 int b:3;
 int c:2;
};

複製代碼

從以上分析能夠看出,位域在本質上就是一種結構類型,不過其成員是按二進位分配的。

位域的使用

位域的使用和結構成員的使用相同,其通常形式爲:

位域變量名.位域名

位域變量名->位域名

位域容許用各類格式輸出。

例子:

void main(){
       //位域
     struct bs {
         unsigned int a:1;//佔 位段a 1 位
         unsigned b:6;//佔 位段b 3 位
         unsigned c:7;//佔 位段c 4 位
     } bit, *pbit;
     // 給位域賦值(應注意賦值不能超過該位域的容許範圍)
     bit.a = 1; //以二進制 1 表示 1 bit位
     bit.b = 50;//以二進制 110010 表示 6 bit位
     bit.c = 100;//以二進制 1100100 標誌 7 bit位
     printf("%d,%d,%d\n",bit.a,bit.b,bit.c);    // 以整型量格式輸出三個域的內容

     pbit=&bit;     //把位域變量 bit 的地址送給指針變量 pbit
     pbit->a=0;     //用指針方式給位域 a 從新賦值,賦爲 0
     pbit->b&=3;     //使用了複合的位運算符 "&=",至關於:pbit->b=pbit->b&3,位域 b 中原有值爲 50,與 3 做按位與運算的結果爲 2(110010&011=010,十進制值爲 2)
     pbit->c|=1;     //使用了複合位運算符"|=",至關於:pbit->c=pbit->c|1,其結果爲 (1100100 | 0000001)= 1100101 = 101
     printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);     //用指針方式輸出了這三個域的值
}

複製代碼

輸出:

1,50,100
0,2,101

複製代碼

18. 共用體

共用體是一種特殊的數據類型,容許您在相同的內存位置存儲不一樣的數據類型。您能夠定義一個帶有多成員的共用體,可是任什麼時候候只能有一個成員帶有值。共用體提供了一種使用相同的內存位置的有效方式。

定義共同體

爲了定義共用體,您必須使用 union 語句,方式與定義結構相似。union 語句定義了一個新的數據類型,帶有多個成員。union 語句的格式以下:

union [union tag]
{
member definition;
member definition;
...
member definition;
}[one or more union variables];

複製代碼

union tag 是可選的,每一個 member definition 是標準的變量定義,好比 int i; 或者 float f; 或者其餘有效的變量定義。在共用體定義的末尾,最後一個分號以前,您能夠指定一個或多個共用體變量,這是可選的。下面定義一個名爲 Data 的共用體類型,有三個成員 i、f 和 str:

union Data
{
int i;
float f;
char str[20];
}

複製代碼

如今,Data 類型的變量能夠存儲一個整數、一個浮點數,或者一個字符串。這意味着一個變量(相同的內存位置)能夠存儲多個多種類型的數據。您能夠根據須要在一個共用體內使用任何內置的或者用戶自定義的數據類型。

共用體佔用的內存應足夠存儲共用體中最大的成員。例如,在上面的實例中,Data 將佔用 20 個字節的內存空間,由於在各個成員中,字符串所佔用的空間是最大的。下面的實例將顯示上面的共用體佔用的總內存大小:

union Data {
    int i;
    float f;
    char str[20];
};
void main(){
    union Data data;
    printf("Memory size occupied by data: %d\n", sizeof(data));
}

複製代碼

輸出:

Memory size occupied by data: 20

複製代碼

訪問共同體成員

爲了訪問共用體的成員,咱們使用成員訪問運算符(.)。成員訪問運算符是共用體變量名稱和咱們要訪問的共用體成員之間的一個句號。您可使用 union 關鍵字來定義共用體類型的變量。下面的實例演示了共用體的用法:

union Data {
    int i;
    float f;
    char str[20];
};

void main() {
    //1. 訪問共同體 no
    data.i = 10;
    data.f = 1314.520;
    strcpy(data.str,"C/C++");
    printf( "data.i : %d\n", data.i);
    printf( "data.f : %f\n", data.f);
    printf( "data.str : %s\n", data.str);

    printf("\n\n\n");
    //2. 訪問共同體 yes
    data.i = 10;
    printf( "data.i : %d\n", data.i);
    data.f = 1314.520;
    printf( "data.f : %f\n", data.f);
    strcpy(data.str,"C/C++");
    printf( "data.str : %s\n", data.str);
 
}

複製代碼

輸出:

data.i : 725823299
data.f : 0.000000
data.str : C/C++



data.i : 10
data.f : 1314.520020
data.str : C/C++

複製代碼

在這裏,咱們能夠看到上面註釋 1 共用體的 if 成員的值有損壞,由於最後賦給變量的值佔用了內存位置,這也是 str 成員可以無缺輸出的緣由。咱們看註釋 2 ,此次咱們在同一時間只使用一個變量成員,因此都能無缺輸出。

19. 位域

參考 17.(位域的介紹)

20. typedef

C 語言提供了 typedef 關鍵字,您可使用它來爲類型取一個新的名字。下面的實例爲單字節數字定義了一個術語 BYTE

typedef unsigned char BYTE;

複製代碼

在這個類型定義以後,標識符 BYTE 可做爲類型 unsigned char 的縮寫,例如:

BYTE  b1, b2;

複製代碼

按照慣例,定義時會大寫字母,以便提醒用戶類型名稱是一個象徵性的縮寫,但您也可使用小寫字母,以下:

typedef unsigned char byte;

複製代碼

您也可使用 typedef 來爲用戶自定義的數據類型取一個新的名字。例如,您能夠對結構體使用 typedef 來定義一個新的數據類型名字,而後使用這個新的數據類型來直接定義結構變量,以下:

typedef struct Books {
    char title[50];
    char author[50];
    char subject[50];
    int book_id;
} Book;

#define TRUE 1
#define FALSE 0

 void main(){
     Book book;
     strcpy( book.title, "C 教程");
     strcpy( book.author, "Runoob");
     strcpy( book.subject, "編程語言");
     book.book_id = 12345;
     printf( "書標題 : %s\n", book.title);
     printf( "書做者 : %s\n", book.author);
     printf( "書類目 : %s\n", book.subject);
     printf( "書 ID : %d\n", book.book_id);

     printf( "TRUE 的值: %d\n", TRUE);
     printf( "FALSE 的值: %d\n", FALSE);
 }

複製代碼

輸出:

書標題 : C 教程
書做者 : Runoob
書類目 : 編程語言
書 ID : 12345
TRUE 的值: 1
FALSE 的值: 0

複製代碼

typedef vs define

define 是 C 指令,用於爲各類數據類型定義別名,與 typedef 相似,可是它們有如下幾點不一樣:

  • typedef 僅限於爲類型定義符號名稱,#define 不只能夠爲類型定義別名,也能爲數值定義別名,好比您能夠定義 1 爲 ONE。
  • typedef 是由編譯器執行解釋的,#define 語句是由預編譯器進行處理的。

例子能夠參考上面是 #define 使用。

21. 輸入 & 輸出

當咱們提到輸入時,這意味着要向程序填充一些數據。輸入能夠是以文件的形式或從命令行中進行。C 語言提供了一系列內置的函數來讀取給定的輸入,並根據須要填充到程序中。

當咱們提到輸出時,這意味着要在屏幕上、打印機上或任意文件中顯示一些數據。C 語言提供了一系列內置的函數來輸出數據到計算機屏幕上和保存數據到文本文件或二進制文件中。

標準輸出

C 語言把全部的設備都看成文件。因此設備(好比顯示器)被處理的方式與文件相同。如下三個文件會在程序執行時自動打開,以便訪問鍵盤和屏幕。

標準文件 文件指針 設備
標準輸入 stdin 鍵盤
標準輸出 stdout 屏幕
標準錯誤 stderr 您的屏幕

文件指針是訪問文件的方式,本節將講解如何從屏幕讀取值以及如何把結果輸出到屏幕上。

C 語言中的 I/O (輸入/輸出) 一般使用 printf() 和 scanf() 兩個函數。

scanf() 函數用於從標準輸入(鍵盤)讀取並格式化, printf() 函數發送格式化輸出到標準輸出(屏幕)。

例子:

void main(){
      float f;
    printf("Enter a float number: \n");
    // %f 匹配浮點型數據
    scanf("%f",&f);
    printf("Value = %f", f);
}

複製代碼

輸出:

Enter a float number: 
12.3
Value = 12.300000

複製代碼

getchar()&putchar() 函數

int getchar(void) 函數從屏幕讀取下一個可用的字符,並把它返回爲一個整數。這個函數在同一個時間內只會讀取一個單一的字符。您能夠在循環內使用這個方法,以便從屏幕上讀取多個字符。

int putchar(int c) 函數把字符輸出到屏幕上,並返回相同的字符。這個函數在同一個時間內只會輸出一個單一的字符。您能夠在循環內使用這個方法,以便在屏幕上輸出多個字符。

void main(){
    int c;
    printf( "\nEnter a value :");
    //函數從屏幕讀取下一個可用的字符,並把它返回爲一個整數。這個函數在同一個時間內只會讀取一個單一的字符。您能夠在循環內使用這個方法,以便從屏幕上讀取多個字符。
    c = getchar( );
    printf( "\nYou entered: ");
    //讀取第一個字符
    putchar( c );
}

複製代碼

輸出:

Enter a value :abcdef

You entered: a

複製代碼

gets() & puts() 函數

char *gets(char *s) 函數從 stdin 讀取一行到 s 所指向的緩衝區,直到一個終止符或 EOF。

int puts(const char *s) 函數把字符串 s 和一個尾隨的換行符寫入到 stdout

void main(){
    char str[100];

    printf( "\nEnter a value :");
    //讀取一行
    gets( str );

    printf( "\nYou entered: ");
    puts( str );
}

複製代碼

輸出:

Enter a value :你們好,纔是真的好!

You entered: 你們好,纔是真的好!

複製代碼

22. 文件讀寫

上一節咱們講解了 C 語言處理的標準輸入和輸出設備。本章咱們將介紹 C 程序員如何建立、打開、關閉文本文件或二進制文件。

一個文件,不管它是文本文件仍是二進制文件,都是表明了一系列的字節。C 語言不只提供了訪問頂層的函數,也提供了底層(OS)調用來處理存儲設備上的文件。本章將講解文件管理的重要調用。

打開文件

您可使用 fopen( ) 函數來建立一個新的文件或者打開一個已有的文件,這個調用會初始化類型 FILE 的一個對象,類型 FILE 包含了全部用來控制流的必要的信息。下面是這個函數調用的原型:

FILE *fopen( const char * filename, const char * mode );

複製代碼

在這裏,filename 是字符串,用來命名文件,訪問模式 mode 的值能夠是下列值中的一個:

模式 描述
r 打開一個已有的文本文件,容許讀取文件。
w 打開一個文本文件,容許寫入文件。若是文件不存在,則會建立一個新文件。在這裏,您的程序會從文件的開頭寫入內容。若是文件存在,則該會被截斷爲零長度,從新寫入。
a 打開一個文本文件,以追加模式寫入文件。若是文件不存在,則會建立一個新文件。在這裏,您的程序會在已有的文件內容中追加內容。
r+ 打開一個文本文件,容許讀寫文件。
w+ 打開一個文本文件,容許讀寫文件。若是文件已存在,則文件會被截斷爲零長度,若是文件不存在,則會建立一個新文件。
a+ 打開一個文本文件,容許讀寫文件。若是文件不存在,則會建立一個新文件。讀取會從文件的開頭開始,寫入則只能是追加模式。

若是處理的是二進制文件,則須要使用下面的訪問模式來取代上面的訪問模式:

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

複製代碼

關閉文件

爲了關閉文件,請使用 fclose( ) 函數。函數的原型以下:

int fclose( FILE *fp );

複製代碼

若是成功關閉文件,fclose( ) 函數返回零,若是關閉文件時發生錯誤,函數返回 EOF。這個函數實際上,會清空緩衝區中的數據,關閉文件,並釋放用於該文件的全部內存。EOF 是一個定義在頭文件 stdio.h 中的常量。

C 標準庫提供了各類函數來按字符或者以固定長度字符串的形式讀寫文件。

寫入文件

下面是把字符串寫入到流中的最簡單的函數:

int fputc(int c,FILE *fp);

複製代碼

函數 fputc() 把參數 c 的字符值寫入到 fp 所指向的輸出流中。若是寫入成功,它會返回寫入的字符,若是發生錯誤,則會返回 EOF。您可使用下面的函數來把一個以 null 結尾的字符串寫入到流中:

int fputs( const char *s, FILE *fp );

複製代碼

函數 fputs() 把字符串 s 寫入到 fp 所指向的輸出流中。若是寫入成功,它會返回一個非負值,若是發生錯誤,則會返回 EOF。您也可使用 int fprintf(FILE *fp,const char *format, ...) 函數來寫把一個字符串寫入到文件中。嘗試下面的實例:

void main(){
      //定義一個空指針文件
    FILE *fp = NULL;
    //打開文件,打開一個文本文件,容許讀寫文件。
    // 若是文件不存在,則會建立一個新文件。
    // 讀取會從文件的開頭開始,寫入則只能是追加模式。
    fp = fopen("/Users/devyk/Data/ClionProjects/NDK_Sample/README.md","a+");
    fprintf(fp, " fprintf 我是添加進來的1\n");
    fprintf(fp, "fprintf 我是添加進來的2\n");
    fputs("fputs 我是添加進來的1\n", fp);
    fputs("fputs 我是添加進來的2\n", fp);
    fclose(fp);
}

複製代碼

讀取文件

下面是從文件讀取單個字符的最簡單的函數:

int fgetc( FILE * fp );

複製代碼

fgetc() 函數從 fp 所指向的輸入文件中讀取一個字符。返回值是讀取的字符,若是發生錯誤則返回 EOF。下面的函數容許您從流中讀取一個字符串:

char *fgets( char *buf, int n, FILE *fp );

複製代碼

函數 fgets() 從 fp 所指向的輸入流中讀取 n - 1 個字符。它會把讀取的字符串複製到緩衝區 buf,並在最後追加一個 null 字符來終止字符串。

若是這個函數在讀取最後一個字符以前就遇到一個換行符 '\n' 或文件的末尾 EOF,則只會返回讀取到的字符,包括換行符。您也可使用 int fscanf(FILE *fp, const char *format, ...) 函數來從文件中讀取字符串,可是在遇到第一個空格和換行符時,它會中止讀取。

例子:

void main(){
    FILE *fp = NULL;
    //讀取文件
    char buff[255];
    fp = fopen("/Users/devyk/Data/ClionProjects/NDK_Sample/README.md","r");
    fscanf(fp,"%s",buff);
    printf("1: %s\n", buff);

    fgets(buff, 255, (FILE*)fp);
    printf("2: %s\n", buff);

    fgets(buff, 255, (FILE*)fp);
    printf("3: %s\n", buff );
    fclose(fp);
}

複製代碼

23. 預處理器

C 預處理器不是編譯器的組成部分,可是它是編譯過程當中一個單獨的步驟。簡言之,C 預處理器只不過是一個文本替換工具而已,它們會指示編譯器在實際編譯以前完成所需的預處理。咱們將把 C 預處理器(C Preprocessor)簡寫爲 CPP。

全部的預處理器命令都是以井號(#)開頭。它必須是第一個非空字符,爲了加強可讀性,預處理器指令應從第一列開始。下面列出了全部重要的預處理器指令:

指令 描述
#define 定義宏
#include 包含一個源代碼文件
#undef 取消已定義的宏
#ifdef 若是宏已經定義,則返回真
#ifndef 若是宏沒有定義,則返回真
#if 若是給定條件爲真,則編譯下面代碼
#else #if 的替代方案
#elif 若是前面的 #if 給定條件不爲真,當前條件爲真,則編譯下面代碼
#endif 結束一個 #if……#else 條件編譯塊
#error 當遇到標準錯誤時,輸出錯誤消息
#pragma 使用標準化方法,向編譯器發佈特殊的命令到編譯器中

例子:

分析下面的實例來理解不一樣的指令。

#define MAX_ARRAY_LENGTH 20

複製代碼

這個指令告訴 CPP 把全部的 MAX_ARRAY_LENGTH 替換爲 20。使用 #define 定義常量來加強可讀性。

#include <stdio.h>
#include "utils.h"

複製代碼

這些指令告訴 CPP 從系統庫中獲取 stdio.h,並添加文本到當前的源文件中。下一行告訴 CPP 從本地目錄中獲取 utils.h,並添加內容到當前的源文件中。

#undef FILE_SIZE
#define FILE_SIZE 42

複製代碼

這個指令告訴 CPP 取消已定義的 FILE_SIZE,並定義它爲 42。

#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

複製代碼

這個指令告訴 CPP 只有當 MESSAGE 未定義時,才定義 MESSAGE。

#ifdef DEBUG
   /* Your debugging statements here */
#endif

複製代碼

這個指令告訴 CPP 若是定義了 DEBUG,則執行處理語句。在編譯時,若是您向 gcc 編譯器傳遞了 -DDEBUG 開關量,這個指令就很是有用。它定義了 DEBUG,您能夠在編譯期間隨時開啓或關閉調試。

預約義宏

ANSI C 定義了許多宏。在編程中您可使用這些宏,可是不能直接修改這些預約義的宏。

描述
DATE 當前日期,一個以 "MMM DD YYYY" 格式表示的字符常量。
TIME 當前時間,一個以 "HH:MM:SS" 格式表示的字符常量。
FILE 這會包含當前文件名,一個字符串常量。
LINE 這會包含當前行號,一個十進制常量。
STDC 當編譯器以 ANSI 標準編譯時,則定義爲 1。

例子:

void main() {
    //這會包含當前文件名,一個字符串常量。
    printf("File :%s\n", __FILE__);
    //當前日期,一個以 "MMM DD YYYY" 格式表示的字符常量。
    printf("Date :%s\n", __DATE__);
    //當前時間,一個以 "HH:MM:SS" 格式表示的字符常量。
    printf("Time :%s\n", __TIME__);
    //這會包含當前行號,一個十進制常量。
    printf("Line :%d\n", __LINE__);
    //當編譯器以 ANSI 標準編譯時,則定義爲 1。
    printf("ANSI :%d\n", __STDC__);
}

複製代碼

輸出:

File :/Users/devyk/Data/ClionProjects/NDK_Sample/day_1/ndk_day1.c
Date :Dec 17 2019
Time :14:23:47
Line :954
ANSI :1

複製代碼

預處理器運算符

C 預處理器提供了下列的運算符來幫助您建立宏:

宏延續運算符()

一個宏一般寫在一個單行上。可是若是宏太長,一個單行容納不下,則使用宏延續運算符(\)。例如:

#define message_for(a, b) \
    printf(#a " and " #b ": We love you!\n")

複製代碼

字符串常量化運算符(#)

在宏定義中,當須要把一個宏的參數轉換爲字符串常量時,則使用字符串常量化運算符(#)。在宏中使用的該運算符有一個特定的參數或參數列表。例如:

#include <stdio.h>

#define message_for(a, b) \ printf(#a " and " #b ": We love you!\n")

int main(void) {
   message_for(Carole, Debra);
   return 0;
}

複製代碼

當上面的代碼被編譯和執行時,它會產生下列結果:

Carole and Debra: We love you!

複製代碼

標記粘貼運算符(##)

宏定義內的標記粘貼運算符(##)會合並兩個參數。它容許在宏定義中兩個獨立的標記被合併爲一個標記。例如:

#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void) {
   int token34 = 40;
   
   tokenpaster(34);
   return 0;
}

複製代碼

當上面的代碼被編譯和執行時,它會產生下列結果:

token34 = 40

複製代碼

這是怎麼發生的,由於這個實例會從編譯器產生下列的實際輸出:

printf ("token34 = %d", token34);

複製代碼

這個實例演示了 token##n 會鏈接到 token34 中,在這裏,咱們使用了字符串常量化運算符(#)標記粘貼運算符(##)

defined() 運算符

預處理器 defined 運算符是用在常量表達式中的,用來肯定一個標識符是否已經使用 #define 定義過。若是指定的標識符已定義,則值爲真(非零)。若是指定的標識符未定義,則值爲假(零)。下面的實例演示了 defined() 運算符的用法:

#include <stdio.h>

#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

int main(void) {
   printf("Here is the message: %s\n", MESSAGE);  
   return 0;
}

複製代碼

當上面的代碼被編譯和執行時,它會產生下列結果:

Here is the message: You wish!

複製代碼

參數化的宏

CPP 一個強大的功能是可使用參數化的宏來模擬函數。例如,下面的代碼是計算一個數的平方:

int square(int x) {
   return x * x;
}

複製代碼

咱們可使用宏重寫上面的代碼,以下:

#define square(x) ((x) * (x))

複製代碼

在使用帶有參數的宏以前,必須使用 #define 指令定義。參數列表是括在圓括號內,且必須緊跟在宏名稱的後邊。宏名稱和左圓括號之間不容許有空格。例如:

#include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(void) {
   printf("Max between 20 and 10 is %d\n", MAX(10, 20));  
   return 0;
}

複製代碼

當上面的代碼被編譯和執行時,它會產生下列結果:

Max between 20 and 10 is 20

複製代碼

24. 頭文件

頭文件是擴展名爲 .h 的文件,包含了 C 函數聲明和宏定義,被多個源文件中引用共享。有兩種類型的頭文件:程序員編寫的頭文件和編譯器自帶的頭文件。

在程序中要使用頭文件,須要使用 C 預處理指令 #include 來引用它。前面咱們已經看過 stdio.h 頭文件,它是編譯器自帶的頭文件。

引用頭文件至關於複製頭文件的內容,可是咱們不會直接在源文件中複製頭文件的內容,由於這麼作很容易出錯,特別在程序是由多個源文件組成的時候。

A simple practice in C 或 C++ 程序中,建議把全部的常量、宏、系統全局變量和函數原型寫在頭文件中,在須要的時候隨時引用這些頭文件。

引用頭文件的語法

使用預處理指令 #include 能夠引用用戶和系統頭文件。它的形式有如下兩種:

#include <file>

複製代碼

這種形式用於引用系統頭文件。它在系統目錄的標準列表中搜索名爲 file 的文件。在編譯源代碼時,您能夠經過 -I 選項把目錄前置在該列表前。

#include "file"

複製代碼

這種形式用於引用用戶頭文件。它在包含當前文件的目錄中搜索名爲 file 的文件。在編譯源代碼時,您能夠經過 -I 選項把目錄前置在該列表前。

引用頭文件的操做

#include 指令會指示 C 預處理器瀏覽指定的文件做爲輸入。預處理器的輸出包含了已經生成的輸出,被引用文件生成的輸出以及 #include 指令以後的文本輸出。例如,若是您有一個頭文件 char_manger.h,以下:

char *test(void);

複製代碼

和一個使用了頭文件的主程序 char_manager.c,以下:

#include "char_manger.h"
int x;
int main (void) {
   puts (test ());
}

複製代碼

編輯器會看到以下的代碼信息:

char *test (void);

int x;

int main (void) {
   puts (test ());
}

複製代碼

只引用一次頭文件

若是一個頭文件被引用兩次,編譯器會處理兩次頭文件的內容,這將產生錯誤。爲了防止這種狀況,標準的作法是把文件的整個內容放在條件編譯語句中,以下:

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

複製代碼

這種結構就是一般所說的包裝器 #ifndef。當再次引用頭文件時,條件爲假,由於 HEADER_FILE 已定義。此時,預處理器會跳過文件的整個內容,編譯器會忽略它。

有條件引用

有時須要從多個不一樣的頭文件中選擇一個引用到程序中。例如,須要指定在不一樣的操做系統上使用的配置參數。您能夠經過一系列條件來實現這點,以下:

#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

複製代碼

可是若是頭文件比較多的時候,這麼作是很不穩當的,預處理器使用宏來定義頭文件的名稱。這就是所謂的有條件引用。它不是用頭文件的名稱做爲 #include 的直接參數,您只須要使用宏名稱代替便可:

#define SYSTEM_H "system_1.h"
 ...
 #include SYSTEM_H

複製代碼

SYSTEM_H 會擴展,預處理器會查找 system_1.h,就像 #include 最初編寫的那樣。SYSTEM_H 可經過 -D 選項被您的 Makefile 定義

25. 強制類型轉換

強制類型轉換是把變量從一種類型轉換爲另外一種數據類型。例如,若是您想存儲一個 long 類型的值到一個簡單的整型中,您須要把 long 類型強制轉換爲 int 類型。您可使用強制類型轉換運算符來把值顯式地從一種類型轉換爲另外一種類型,以下所示:

(type_name) expression

複製代碼

請看下面的實例,使用強制類型轉換運算符把一個整數變量除以另外一個整數變量,獲得一個浮點數:

void main(){
   void main(){
     int sum = 20,count = 3;
     double  value,value2;
    value = (double)sum / count;
    value2 = sum / count;
    printf("Value 強轉 : %f Value2 wei強轉 : %f\n ", value ,value2);
 }
}

複製代碼

輸出:

Value 強轉 : 6.666667 Value2 wei強轉 : 6.000000

複製代碼

整數提高

整數提高是指把小於 intunsigned int 的整數類型轉換爲 intunsigned int 的過程。請看下面的實例,在 int 中添加一個字符:

void main(){
  
    //整數提高
    int i= 17;
    char c = 'c'; //在 ascii 中的值表示 99
    int sum2;

    sum2 = i + c;
    printf("Value of sum : %d\n", sum2 );
}

複製代碼

輸出:

Value of sum : 116

複製代碼

在這裏,sum 的值爲 116,由於編譯器進行了整數提高,在執行實際加法運算時,把 'c' 的值轉換爲對應的 ascii 值。

26. 錯誤處理

C 語言不提供對錯誤處理的直接支持,可是做爲一種系統編程語言,它以返回值的形式容許您訪問底層數據。在發生錯誤時,大多數的 C 或 UNIX 函數調用返回 1 或 NULL,同時會設置一個錯誤代碼 errno,該錯誤代碼是全局變量,表示在函數調用期間發生了錯誤。您能夠在 errno.h 頭文件中找到各類各樣的錯誤代碼。

因此,C 程序員能夠經過檢查返回值,而後根據返回值決定採起哪一種適當的動做。開發人員應該在程序初始化時,把 errno 設置爲 0,這是一種良好的編程習慣。0 值表示程序中沒有錯誤。

errno、perror() 和 strerror()

C 語言提供了 perror()strerror() 函數來顯示與 errno 相關的文本消息。

  • perror() 函數顯示您傳給它的字符串,後跟一個冒號、一個空格和當前 errno 值的文本表示形式。
  • strerror() 函數,返回一個指針,指針指向當前 errno 值的文本表示形式。

讓咱們來模擬一種錯誤狀況,嘗試打開一個不存在的文件。您可使用多種方式來輸出錯誤消息,在這裏咱們使用函數來演示用法。另外有一點須要注意,您應該使用 stderr 文件流來輸出全部的錯誤。

例子:

void main(){
    int dividend = 20;
    int divsor = 0;
    int quotient;

    if (divsor == 0){
        fprintf(stderr,"除數爲 0 退出運行。。。\n");
        exit(EXIT_FAILURE);
    }
    quotient = dividend / divsor;
    fprintf(stderr,"quotient 變量的值爲 : %d\n", quotient);
    exit(EXIT_SUCCESS);
}

複製代碼

輸出:

除數爲 0 退出運行。。。

複製代碼

27. 遞歸

遞歸指的是在函數的定義中使用函數自身的方法。

語法格式以下:

void recursion() {
   statements;
   ... ... ...
   recursion(); /* 函數調用自身 */
   ... ... ...
}
 
int main() {
   recursion();
}

複製代碼

數的階乘

double factorial(unsigned int i){
    if (i <= 1){
        return 1;
    }
   return i * factorial(i - 1);
}
void main(){
    int i = 15;
    printf("%d 的階乘 %ld \n",i ,factorial(i));
}

複製代碼

輸出:

15 的階乘 140732727129776 

複製代碼

斐波拉契數列

//斐波拉契數列
int fibonaci(int i){
    if (i == 0){
        return 0;
    }
    if (i == 1){
        return 1;
    }
    return fibonaci(i - 1) + fibonaci( i -2);
}
void main(){
    for (int j = 0; j < 10; j++) {
        printf("%d\t\n", fibonaci(j));

    }
}

複製代碼

輸出:

0
1	
1	
2	
3	
5	
8	
13	
21	
34	

複製代碼

28. 可變參數

有時,您可能會碰到這樣的狀況,您但願函數帶有可變數量的參數,而不是預約義數量的參數。C 語言爲這種狀況提供了一個解決方案,它容許您定義一個函數,能根據具體的需求接受可變數量的參數。下面的實例演示了這種函數的定義。

int func(int, ... ) {
   .
   .
   .
}
 
int main() {
   func(2, 2, 3);
   func(3, 2, 3, 4);
}

複製代碼

請注意,函數 func() 最後一個參數寫成省略號,即三個點號(...),省略號以前的那個參數是 int,表明了要傳遞的可變參數的總數。爲了使用這個功能,您須要使用 stdarg.h 頭文件,該文件提供了實現可變參數功能的函數和宏。具體步驟以下:

  • 定義一個函數,最後一個參數爲省略號,省略號前面能夠設置自定義參數。
  • 在函數定義中建立一個 va_list 類型變量,該類型是在 stdarg.h 頭文件中定義的。
  • 使用 int 參數和 va_start 宏來初始化 va_list 變量爲一個參數列表。宏 va_start 是在 stdarg.h 頭文件中定義的。
  • 使用 va_arg 宏和 va_list 變量來訪問參數列表中的每一個項。
  • 使用宏 va_end 來清理賦予 va_list 變量的內存。

如今讓咱們按照上面的步驟,來編寫一個帶有可變數量參數的函數,並返回它們的平均值:

例子:

double average(int num,...){
     va_list  vaList;
     double  sum = 0.0;
     int i ;
     //爲 num 個參數初始化 valist
     va_start(vaList,num);
     //訪問全部賦給 vaList 的參數
    for (int j = 0; j < num; j++) {
        sum += va_arg(vaList, int);
    }
    //清理爲valist 保留的內存
    va_end(vaList);
    return sum/num;
 }

 void main(){
     printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
     printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
 }

複製代碼

輸出:

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000

複製代碼

29. 內存管理

本章將講解 C 中的動態內存管理。C 語言爲內存的分配和管理提供了幾個函數。這些函數能夠在 <stdlib.h> 頭文件中找到。

序號 函數和描述
*void calloc(int num, int size); 在內存中動態地分配 num 個長度爲 size 的連續空間,並將每個字節都初始化爲 0。因此它的結果是分配了 numsize 個字節長度的內存空間,而且每一個字節的值都是0。
void free(void *address); 該函數釋放 address 所指向的內存塊,釋放的是動態分配的內存空間。
void *malloc(int num); 在堆區分配一塊指定大小的內存空間,用來存放數據。這塊內存空間在函數執行完成後不會被初始化,它們的值是未知的。
void *realloc(void *address, int newsize); 該函數從新分配內存,把內存擴展到 newsize

**注意: ** void * 類型表示未肯定類型的指針。C、C++ 規定 void * 類型能夠經過類型轉換強制轉換爲任何其它類型的指針。

動態分配內存

編程時,若是您預先知道數組的大小,那麼定義數組時就比較容易。例如,一個存儲人名的數組,它最多容納 100 個字符,因此您能夠定義數組,以下所示:

char name[100];

複製代碼

可是,若是您預先不知道須要存儲的文本長度,例如您向存儲有關一個主題的詳細描述。在這裏,咱們須要定義一個指針,該指針指向未定義所需內存大小的字符,後續再根據需求來分配內存,以下所示:

void main() {
    char name[100];
    char *description;

    //將字符串 copy 到 name 中
    strcpy(name, "迎娶白富美!");

    //開始動態分配內存
    description = (char *) malloc(200 * sizeof(char));
    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    } else {
        strcpy(description, "開始添加數據到 description 中");
    }
    printf("Name = %s\n", name );
    printf("Description: %s sizeOf 大小 :%d\n", description , sizeof(description));
// 使用 free() 函數釋放內存
    free(description);
}

複製代碼

輸出:

Name = 迎娶白富美!
Description: 開始添加數據到 description 中 sizeOf 大小 :8

複製代碼

30. 命令行參數

執行程序時,能夠從命令行傳值給 C 程序。這些值被稱爲命令行參數,它們對程序很重要,特別是當您想從外部控制程序,而不是在代碼內對這些值進行硬編碼時,就顯得尤其重要了。

命令行參數是使用 main() 函數參數來處理的,其中,argc 是指傳入參數的個數,argv[] 是一個指針數組,指向傳遞給程序的每一個參數。下面是一個簡單的實例,檢查命令行是否有提供參數,並根據參數執行相應的動做:

void main(int argc , char *argv[]){
    if (argc ==1){
        printf("argv[%d] == %d",0,*argv[0]);
    }
    else if (argc ==2){
        printf("argv[%d] == %d",1,*argv[1]);
    } else{
        printf("匹配失敗...");
    }
}

複製代碼

輸出:

argv[0] == 47

複製代碼

總結

不知道你們在看完 C 基礎內容以後在對比下 Java 語法,是否是大部分都差很少,以前有的人說學了 C 在學其它語言都是小菜一碟,如今看來好像是這麼回事。我的以爲其實只要會編程語言中的任何一門在學其它語言都會比較容易上手。

C 語言基礎增強學習資料

參考

相關文章
相關標籤/搜索