Linux C語言編程基本原理與實踐

Linux C語言編程基本原理與實踐(2018-06-16 19:12:15)linux

Linux C語言編程基本原理與實踐

高效的學習帶着目的性: 是什麼 -> 幹什麼 -> 怎麼用nginx

重識C語言

  1. C語言是一種通用的, 面向過程的編程語言, 在系統與應用軟件的開發應用較廣
  2. 是人類和計算機交流的一種方式
  3. ANSI C: 是C語言的標準, 爲了不各開發商用的C語言語法的差別
  4. C語言的特色: 簡單, 快速, 高性能, 兼容性好, 功能強大, 易於學習

mark

C語言適合作什麼

  1. Linux嵌入式, 小工具(命令行下的cd, ls之類的命令) 小巧靈活,語法簡單,適合作小工具
  2. 與硬件打交道的程序: 操做系統, ARM嵌入式, 單片機編程以及Arduino編程等等
  3. 對性能要求較高的應用程序: NGINX(C)的併發量 = Apache(C++) * 10

C適合領域

  1. 小工具(語法簡單)
  2. 和硬件打交道的程序 ARM嵌入式,單片機,Arduino編程(有指針,可操做內存)
  3. 有高性能要求的程序
nginx:c apache:c++

linux嵌入式c++

開發環境與配置:

  • C語言是隨着UNIX誕生而產生的一門編程語言
  • Mac電腦是Unix內核; Windows下能夠安裝Linux虛擬機

Ubuntu:apache

  1. Ubuntu和CentOS是較爲經常使用的Linux發行版本, 我的電腦用Ubuntu更好
  2. Ubuntu的kylin版對中文支持很好
  3. amd64版: AMD當初率先推出64位CPU, 因此Ubuntu把64位CPU型號定義爲amd64(Intel照用), 一直沿用到現今; 32位用x86
  4. LTS版: 長時間的技術支持版本
  5. 裝Ubuntu系統能夠選擇雙系統, 也可在原來的Windows電腦上裝虛擬機

PS: 儘可能在Linux環境下開發C語言程序編程

經常使用指令

  • 終端編輯器:emacs vim
  • 安裝軟件:sudo apt install 【軟件名】
  • 更新軟件:sudo apt update
  • Ctrl+Alt+T:打開終端
  • cd ~ :進入當前用戶的根目錄
  • pwd :查看當前所在路徑
  • ls :當前章目錄包含哪些文件
  • ls -l :顯示當前文件的類型,權限,建立時間,名字
  • ls -alll:顯示隱藏文件
若是前面是 d就是文件夾, -就是普通類型的文件
  • touch ** :建立字符型文件
  • rm ** :刪除
  • mkdir ** :建立目錄(文件夾)
  • vi ** :打開(進入)文件
vi 一個不存在的文件,進入後沒法輸入內容,因當前在命令模式下;按字符i,可進入INSERT插入模式,就可輸入內容,按Esc返回命令模式;

命令模式下:ubuntu

  • :w :保存該文件
  • :q :退出
  • i :當前光標前面插入字符
  • Shift+i :跳到本行行首插入字符
  • a :當前光標後面插入字符
  • Shift+a :跳到本行末尾插入字符
  • o :在當前下一行插入字符
  • Shift+o :在當前上一行插入字符
  • x :刪除當前光標所處字符
  • d+d :刪除整行

Linux下最好用的文本編輯器: emacs, vim小程序

  • cc -v(gcc -v): 查看編譯器版本
  • apt-get是一條linux命令,適用於deb包管理式的操做系統,主要用於自動從互聯網的軟件倉庫中搜索、安裝、升級、卸載軟件或操做系統。
  • clear:清潔屏幕

Linux下第一個C程序

linux下通常不用void main,最新c語言標準,int mainvim

#include <stdio.h>

int main()
{
    printf("hello,world!\n");
    return 0;
}
cc a.c

默認會爲咱們編譯並生成可執行文件a.out(可讀可寫可執行)數組

mark

  • ./表示當前路徑下,
  • ./a.out 執行當前路徑下的a.out文件
  • r表示可讀 w表示可寫 x表示可執行

三組重複的順序爲"建立者","用戶組","任意其餘用戶"併發

多個源文件分而治之

c語言是一個結構化的程序語言,是支持多函數的。程序可由若干個函數組成。

vim hello.c

最原始版本的實現(hello.c):

#include <stdio.h>

int max(int a, int b)
{
    if(a>b){
        return a;      
    }else{
        return b;
    }
}

int main()
{
    int a1 = 33;
    int a2 = 21;
    int maxNum = max(a1,a2);
    printf("the max value is %d\n",maxNum);
    return 0;
}
  • 咱們的stdio.h是在咱們的user/include中被內置了

mark

  • 在編寫max函數時對齊,編寫內部時括號進行縮進對齊。

mark

附加知識: vim分屏顯示

  • :sp 文件名 //建立(打開)新文件
  • 上屏: ctrl+w+上箭頭
  • 下屏: ctrl+w+下箭頭
  • 打開行號 :set nu
  • 剪切:(最後一行行數)+dd
  • 粘貼:p

//這兩個不用點冒號

mark

  • 關閉行號:set nonu

mark

若是就是上圖代碼直接編譯會報錯,這是一個未聲明的函數。

有兩種分離方案:

  • 第一種是int max(int a,int b);,在hello.c中聲明該方法,而後編譯的時候須要加上max.c
  • 一種是#include "max.c" 而後編譯的時候就不須要加上max.c一塊兒編譯

版本1:

0-hello.c:

#include <stdio.h>
int max(int a,int b);

int main()
{
    int a1 = 33;
    int a2 = 21;
    int maxNum = max(a1,a2);
    printf("the max value is %d\n",maxNum);
    return 0;
}

0-max.c:

int max (int a, int b)
{
    if(a>b){
        return a;
    }else{
        return b;
    }
}

編譯命令:

gcc 0-hello.c 0-max.c -o 0-hello.out

若是不加上0-max.c一塊兒編譯,會出現錯誤

gcc 0-hello.c -o 0-hello.out
/tmp/cc8GuaAH.o:在函數‘main’中:
0-hello.c:(.text+0x21):對‘max’未定義的引用
collect2: error: ld returned 1 exit status

版本2

1-hello.c:

#include <stdio.h>
#include "0-max.c"

int main()
{
    int a1 = 33;
    int a2 = 21;
    int maxNum = max(a1,a2);
    printf("the max value is %d\n",maxNum);
    return 0;
}

0-max.c與原來的一致

編譯命令:

gcc 1-hello.c -o 1-hello.out

若是此時多加了0-max.c一塊兒編譯

gcc 1-hello.c 0-max.c -o 1-hello.out
/tmp/ccjcCmVa.o:在函數‘max’中:
0-max.c:(.text+0x0): `max'被屢次定義
/tmp/cclIxMtD.o:1-hello.c:(.text+0x0):第一次在此定義
collect2: error: ld returned 1 exit status

終端下:

  • gcc 文件名.c -o 命名.out
  • 生成.out並命名
  • #include <>表示在預裝的庫裏查找
  • #include "max.c"表示在當前目錄內查找文件
include "max.c"至關於把整個函數複製進來了。效果等同於寫進來
  • wqa 是將多個文件一塊兒保存

頭文件與函數定義分離

把函數的聲明和定義分離開來

代碼沒有main函數不能執行,main是入口。

  • .h 頭文件
  • .o 編譯以後的中間文件
  • .c 源代碼
mtianyan@ubuntu:~/Desktop/zjuPlan/CSF878/CCode/linux_c/2-lesson/part1$ ls
0-max.c  1-hello.c

加快編譯速度

gcc -c 0-max.c -o 0-max.o

將max.c變成max.o以後,咱們須要把hello.c中的include註釋掉並添上方法聲明

#include <stdio.h>
//#include "0-max.c"
int max(int a,int b);

可讀可寫不可執行,max.o至關於計算器對於源代碼進行了翻譯,變成計算機可識別的機器碼

gcc 0-max.o 1-hello.c -o 1-hello.out

新建一個min.c

int min (int a, int b)
{
    if(a<b){
        return a;
    }else{
        return b;
    }
}

hello.c中進行minNum的調用

#include <stdio.h>
//#include "0-max.c"
int max(int a,int b);
int min(int a,int b);

int main()
{
    int a1 = 33;
    int a2 = 21;
    int maxNum = max(a1,a2); 
    int minNum = min(a1,a2);
    printf("the max value is %d\n",maxNum); 
    printf("the min value is %d\n",minNum);
    return 0;
}

編譯命令:

gcc -c min.c -o min.o
gcc 0-max.o min.o 1-hello.c -o 2-hello.out

加快編譯速度。不會再修改的函數,公共框架和公共類編譯生成靜態庫。

gcc的編譯流程分爲4步:

預處理(Pre-Processing) -> 編譯(Compling) -> 彙編(Assembling) -> 鏈接(Linking)

預處理: 處理#include #define #ifdef 等宏命令

編譯: 把預處理完的文件編譯爲彙編程序.s

彙編: 把彙編程序.s編譯爲.o二進制文件

  • gcc -c min.c -o min.o //把文件min.c預編譯成文件min.o
  • cp 文件名a 文件b //把文件a拷貝成新的文件b
  • 使用yy複製一行 使用 行號n+yy複製n行
  • 使用p對複製的行進行粘貼
  • 源代碼文件用cat命令能夠查看

那麼問題又來了,咱們如今的max函數和min函數是本身編寫的,即便不加聲明,咱們也知道須要哪些參數,參數是什麼類型,返回值是什麼類型。若是這個函數不是咱們編寫的,別人又編寫成了max.o min.o 咱們看不到源代碼,又不知道函數的傳入參數與返回。.h文件的好處就來了

咱們建立一個文件夾part2

max.h 代碼:

int maxNum(int a, int b);

min.h 代碼:

int minNum(int a ,int b);

hello.c代碼:

#include <stdio.h>
#include "max.h"
#include "min.h"

int main()
{
    int a1 = 33;
    int a2 = 21;
    int maxNum = max(a1,a2); 
    int minNum = min(a1,a2);
    printf("the max value is %d\n",maxNum);
    printf("the min value is %d\n",minNum);
}

mark

gcc max.o min.o hello.c -o hello.out
warning: implicit declaration of function ‘max’; did you mean ‘main’?
出現警告,但不影響正常的編譯運行。

makFile的編寫

  • make工具能夠將大型的開發項目分紅若干個模塊
  • make -v
  • sudo apt-get install make
  • make工具能夠很清晰和很快捷的整理源文件

make工具的內部也是使用的gcc

咱們本身開發仍是安裝軟件都要使用到 makemake install這兩個命令

由於當咱們的源文件不少不少的時候

gcc max.c min.c hello.c -o hello.out

命令就會很長很長。

編寫一個Makefile能夠同時編譯多個文件,告訴依賴關係。

  • vim Makefile
  • 寫入:
hello.out:max.o min.o hello.c
      gcc max.o min.o hello.c -o hello.out
max.o:max.c
      gcc -c max.c
min.o:min.c
      gcc -c min.c
# 添加註釋
  • 執行命令make
編寫 Makefile 縮進使用 tab 鍵(八個空格,不然出錯)
Makefile:3: *** 遺漏分隔符 (null)。 中止。
  • 重命名文件 mv MakeFile makefile

從上往下找,從下往上編譯出來。 已經生成出來的文件不會再從新生成。

make: 「hello.out」已經是最新。 (up to date)

詳細講解main函數中的返回值的做用以及main函數中的參數意義

mark

main.c:

#include <stdio.h>

int main(int argc,char* argv[]) //main函數完整形式
{
    printf("hello,world\n");
    return 101;
}
gcc main.c -o main.out && ./main.out
  • gcc main.c -o main.out && ./ main.out 能夠依次執行兩條命令
  • return 0 用來驗證程序運行是否成功。
  • 命令echo $?用來查看返回值

mark

./main.out && ls打印出helloworld的同時,列出當前目錄。由於main.out的return值爲0,斷定爲成功執行。

./main2.out && ls打印出helloworld,未列出當前目錄。由於main.out的return值爲101(非0),斷定爲不成功執行。

main函數中的參數

int main(int argc, char* argv[])//main函數完整形式

argc統計輸入參數的個數。只輸入文件名時個數爲1

argv[]存放每一個參數的內容
如:

  • 輸入 ./main3.out -l -a
  • argv = 3
  • argc[0] = ./main.out
  • argc[1] = -l
  • argc[2] = -a
argc(argument count),存放着傳入main函數的參數個數是幾個。

argv(argument vector),存放具體傳入的參數

main3.c:

#include <stdio.h>

int main(int argc,char* argv[])
{
    printf("argc is %d\n",argc);
    //int i;
    for(int i =0;i<argc;i++){
    printf("argv[%d]is %s\n",i,argv[i]);
    }

    return 101;
}
$ ./main3.out -a -l
argc is 3
argv[0]is ./main3.out
argv[1]is -a
argv[2]is -l

Linux下的標準輸入流輸出流與錯誤流

  • C語言是如何被操做系統調用的
  • 操做系統如何傳遞參數
  • main函數的返回值含義

linux把全部東西看成文件處理

  • 標準輸入流文件:stdin 鍵盤
  • 標準輸出流文件:stdout 顯示器
  • 標準錯誤流文件:stderr
  • 這是系統默認建立
  • fprintf fscanf:將輸入輸出放入...
  • printf("") 是對fprintf(stdout,"")函數的封裝.
  • scanf("") 是對fscanf(stdin,"")函數的封裝
#include <stdio.h>

int main()
{
    // printf("hello world!\n");
    fprintf(stdout,"hello world \n");
    int a;
    //scanf("%d",&a);
    fscanf(stdin,"%d",&a);
    if(a<0){
     fprintf(stderr,"the value must >0\n");
     # 返回值不等於0代表函數出錯
     return 1;
    }

    //printf("input value is: %d\n",a);
    return 0;

}

輸出輸出流與錯誤流的重定向

Linux幾乎能夠用於任何領域,這裏咱們不得不提出linux的通道。
管道起到了很重要的做用,不一樣應用程序之間要配合使用,就須要用到管道。

Demo:main.c

先理解輸入流,輸出流和錯誤流的 重定向機制,對於管道的理解會比較容易些。
#include <stdio.h>

int main()
{
int i,j;
printf("input the int value i:\n"); \\printf其實對fprintf的封裝,是從標準輸出流(即stdout)來輸出這個過程
scanf("%d", &i); //默認輸入流是鍵盤
printf("input the int value j:\n");
scanf("%d", &j);
printf("i+j=%d\n", i+j);
}
  • 執行編譯命令cc main.c, 獲得a.out,運行a.out,咱們分別輸入3和5輸入到終端.
  • 咱們可使用命令./a.out 1>> a.txt,其中>>符號(不寫參數就是輸出流),以前默認輸出流是終端,如今咱們則改成輸出到a.txt中,咱們執行命令後,分別輸入3回車後再輸入5。再使用命令cat a.txt,咱們能夠看到咱們已經輸出到文件裏的內容。

mark

咱們標準輸出流是1>>,輸入流是0>>

  • 咱們再次執行./a.out >> a.txt,咱們再次輸入參數,完成後咱們再次使用cat來查看a.txt文件裏的內容,發現以前的內容還在,新的輸出內容追加到了後面
  • 再舉一個重定向的例子,咱們使用命令ls /etc >> etc.txt,咱們將ls目錄下的內容輸入到了etc.txt文件中
  • 但咱們如若改重定向符號想覆蓋掉以前的內容,能夠把雙箭頭>>改成單箭頭>,則文件中先前的內容就會被覆蓋掉。

輸入流重定向

  • 咱們能夠建立一個文件vi input.txt,內容以下:
18
9
  • 咱們再次執行./a.out < input.txt,不存在追加模式,因此咱們用單箭頭<,咱們能夠將要輸入的內容所有在input.txt中準備好,命令執行後,咱們便在終端上能夠看到結果。

mark

#include <stdio.h>

int main()
{
    int i,j;
    printf("input the int value i:\n"); 
    scanf("%d", &i); //默認輸入流是鍵盤
    printf("input the int value j:\n");
    scanf("%d", &j);
    if(0!=j){    
        printf("%d/%d=%d\n",i,j,i/j);
    }else{
        fprintf(stderr,"j != 0\n");
        return 1;
    
    }


    return 0;
}
注意代碼規範小技巧,0和j的比較,0放前面若是漏了等號會報錯

echo $? 查看返回值

./main.out 1>true.txt 2>false.txt < input.txt
  • 將輸出保存到t.txt.
  • 錯誤保存到f.txt.
  • 從input.txt讀入數據

mark

而當值錯誤,產生除零錯誤時。

mark

上圖是當j=0時的運行狀況。

mark

重定向到其餘地方。

管道原理及應用。

管道:

把前面的輸出流做爲後面工具的輸入流,用一個|表示.

  • grep :查看指定文本,搜出包含字符的文本
  • 終端中輸入: ls /etc/ | grep ab
  • ls /etc/ 的輸出做爲grep的輸入
  • ls /etc/ | grep ab 在/etc/文件下查找含有ab字符的文件名

mark

  • ps -e:查看系統運行的進程
ps -e | grep ssh

mark

查看包含ssh的進程

實踐編程,用簡單的C語言代碼,編寫一個實用的C語言小程序

avg.c :

#include <stdio.h>

int main()
{
    int s,n;
    scanf("%d,%d",&s,&n);
    float v = s/n;
    printf("v =%f\n",v);
    return 0;
}

編譯命令:

cc avg.c -o avg.out

input.c:

#include <stdio.h>

int main()
{
    int flag =1;
    int i;
    int s = 0;
    int count=0;
    while(flag){
    scanf("%d",&i);
    if(0==i) break; #等於零跳出
    count++;
    s+=i;
    }
    printf("%d,%d\n",s,count);
    return 0;
}
cc input.c -o input.out

mark

注意上面的代碼中應該將s初始化爲0.不然會產生問題。

使用時:

./input.out | ./avg.out

實現將input的數據之間求平均值。

相關文章
相關標籤/搜索