1. 程式人生 > >C語言入門語法

C語言入門語法

一、資料型別

常量

1、通過預處理宣告常量

#include <stdio.h>

#define PRICE 100

int main() {
    printf("價格:%d\n",PRICE);
    return 0;
}

2、通過 const 關鍵字宣告常量

#include <stdio.h>
const int NUM=10;
int main() {
    printf("數量:%d\n",NUM);
    return 0;
}

區別:#define 在執行時會替換為指定的值,const可以看到資料型別,更推薦使用 const

整型

在C語言中分為有符號和無符號整型,無符號是整數,有符號包含負數

型別 說明
short 2位元組
int 4位元組
long 32位系統4位元組,64位系統8位元組
long long 8位元組
int main() {
    int a=10;
    int b=-10;
    long long c=20;
    int d=0b111;//二進位制
    int e=0xb;//十六進位制
    int f=010;//八進位制
    unsigned int g=12;//無符號正數
    printf("a=%d,b=%d,c=%d,d=%d",a,b,c,d);
    return 0;
}
  • 0b 為二進位制方式賦值
  • 0x 為十六進位制資料
  • 0 為八進位制資料

進位制相關知識參考其他教程

c11 標準 stdint.h 對資料的長度進行了統一

型別 說明
int8_t 統一8位1位元組
int16_t 2位元組
int32_t 4位元組
int64_t 8位元組
uint8_t 無字元1位元組

浮點數

型別 說明
float 單精度,4位元組,32位
double 雙精度,8位元組,64位
long double 長雙精度,16位元組,128位

typedef 自定義型別

typedef uint8_t mychar;
int main() {
    mychar ch='a';
    printf("%c",ch);
    return 0;
}

自定義型別相當於對型別起了個別名

二、goto 跳轉

使用 goto 實現迴圈

int i=0;
aa:
printf("%d\n",i);
i=i+1;
if(i<100){
    goto aa;
}

標籤名: 標記一個位置,goto 標籤名; 跳轉到指定標籤位置,標籤名可以自己定義。

三、輸入與輸出

相關函式文件參考:C語言stdio.h文件

1、字元輸出

putchar:寫字元到標準輸出,相當於呼叫 putc
遍歷字元並列印

#include <stdio.h>

int main() {
    char c;
    for (c='a';c<='z';c++) {
        putchar(c);
        putchar('\n');
    }
    return 0;
}

說明:char 型別佔用1位元組,每個字元都對映對應一個整數。

檢視字元對應整數

#include <stdio.h>

int main() {
    char c;
    printf("=====小寫====\n");
    for (c='a';c<='z';c++) {
        printf("%d\n",c);
    }
    printf("=====大寫====\n");
    for (c='A';c<='Z';c++) {
        printf("%d\n",c);
    }
    return 0;
}

大寫字元和小寫字元剛好差距32, putchar('A'+32); 可轉為小寫。

2、字串輸出

puts:寫字串到標準輸出

輸出字串示例

#include <stdio.h>

int main() {
    char string [] = "Hello world!";
    puts(string);
    return 0;
}

3、格式化輸出

printf:列印格式化資料到標準輸出,如果包含格式說明符(以%開頭的子串)將格式化,用附加引數替換說明符(說明符可以認為是佔位符)。

格式化時用到的說明符

符號 解釋 舉例
d 或者 i 有符號十進位制整數 392
u 無符號十進位制整數 7235
o 無符號八進位制 610
x 無符號十六進位制整數 7fa
X 無符號十六進位制整數(大寫) 7FA
f 十進位制浮點,小寫 392.65
F 十進位制浮點,大寫 392.65
e 科學記數法(尾數/指數),小寫 3.9265e+2
c 字元 a
s 字串 hello
p 指標地址(記憶體地址) b8000000
% %%打印出一個% %

格式化輸出示例

#include <stdio.h>

int main() {
    printf("字元:%c %c\n",'a',65);
    printf("整數:%d %ld\n",600,6500000L);//%ld 長整
    printf("浮點數:%f\n",33.3);
    printf("十六進位制:%x\n",0xffa);
    printf("特殊列印:%%\n");
    return 0;
}

結果

字元:a A
整數:600 6500000
浮點數:33.300000
十六進位制:ffa
特殊列印:%

4、輸入字元和字串

getchar:從標準輸入中獲取字元

獲取字元示例

#include <stdio.h>

int main() {
    uint8_t c;
    puts("輸入文字,在一個句子中包含.以結束:");
    do{
        c=getchar();
        putchar(c);
    }while (c!='.');
    return 0;
}

uint8_t c; uint8_t 大小與char一樣

gets:從標準輸入中獲取字串

獲取字串示例

#include <stdio.h>

int main() {
    char str[256];
    puts("輸入你的名字:");
    gets(str);
    printf("你的名字:%s\n",str);
    return 0;
}

5、格式化輸入

scanf:從標準輸入中讀取格式化資料,格式化說明符可參照 printf

格式化輸入示例

#include <stdio.h>

int main() {
    char ch;
    int num;
    char str[10];

    puts("輸入一個字元:");
    scanf("%c",&ch);
    printf("使用者的輸入字元為:%c\n",ch);

    puts("輸入一個整數:");
    scanf("%d",&num);
    printf("使用者輸入的整數為:%d\n",num);

    puts("輸入字串:");
    scanf("%s",str);
    puts(str);
    return 0;
}

int、char 等型別需要用&符號取得變數記憶體地址,變數 str 是一個數組,本身儲存的就是記憶體地址,所以不用加上&符號。(可以理解為值與引用)

四、陣列

1、一維陣列

建立陣列方式1:

const int NUMS_LEN=10;
int nums[NUMS_LEN];

陣列中元素的記憶體地址沒有做過任何處理,有可能被之前其他程式使用過,所以陣列中的預設的值是不可預期的。

演示陣列中元素預設值:

#include <stdio.h>

int main() {
    const int NUMS_LEN=10;
    int nums[NUMS_LEN];
    for (int i = 0; i < NUMS_LEN; ++i) {
        printf("索引:%d,值:%d\n",i,nums[i]);
    }
    return 0;
}

建立陣列方式2:

int ages[]={19,20,30};

2、二維陣列

建立方式1:

int nums[3][4];
for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 4; ++j) {
        printf("%d,%d,value=%d\n",i,j,nums[i][j]);
    }
}

建立方式2:

int nums[2][3]={
        {1,2,3},
        {4,5,6}
};

二維陣列直接初始化值需要指定長度!

如果想對陣列中的元素清0,可以在遍歷中為元素賦值為0.

3、字元陣列(字串)

宣告字串並檢查長度:

char str[10];
printf("長度:%ld\n",strlen(str));

strlen 函式用於檢測字元陣列中字元的長度

存入字串並檢查長度:

char str[10]="hello";
printf("長度:%ld\n",strlen(str));

\0作為字串結尾:

char str[10]="he\0llo";
printf("長度:%ld\n",strlen(str));

字串中不論內容多少,只要遇到 \0 就認為字串結束!

char陣列填充函式 memset:

memset(要填充的陣列,要填充的值,要填充多長)

#include <stdio.h>
#include <string.h>
int main() {
    char str[]="你好";
    memset(str,1,6);
    for (int i = 0; i < 6; ++i) {
        printf("%d\n",str[i]);
    }
    return 0;
}

關於中文:

char str[2]="你好";
printf("長度:%ld\n",strlen(str));//一個漢字佔用3位元組

五、字串操作

1、字串連線

strcat:連線字串,將源字串的副本附加到目標字串。

#include <stdio.h>
#include <string.h>
int main() {
    char * str="hello";//要拼接的字元
    char result[100];//目標字元
    memset(result,0,100);//填充0到目標字元
    strcat(result,str);//拼接str到result
    strcat(result,"world");//拼接 world 到result
    puts(result);
    return 0;
}

strncat:連線指定數量的字元到目標字串

#include <stdio.h>
#include <string.h>
int main() {
    char dist[10];
    strncat(dist,"hello",3);
    puts(dist);
    return 0;
}

結果

hel

2、格式化字串

sprintf:將格式化資料寫入字串(格式方式和其他格式化方法很像)

#include <stdio.h>
#include <string.h>
int main() {
    char str[10];
    memset(str,0,10);
    int len,a=10,b=5;
    len=sprintf(str,"%d+%d=%d",a,b,a+b);
    printf("格式結果:[%s],長度為:%d",str,len);
    return 0;
}

結果:

格式結果:[10+5=15],長度為:7

3、字串與基礎資料型別轉換

sscanf:從字串中讀取格式化資料

使用 sscanf 擷取資料

#include <stdio.h>
#include <string.h>
int main() {
    //原字串
    char sentence[]="Java is 23 years old";
    //儲存拆分出來的資料的變數
    int age;
    char name[10];
    sscanf(sentence,"%s %*s %d",name,&age);
    printf("age is %d,name is %s",age,name);
    return 0;
}

"%s %*s %d" 分別表示了 Java is 23 (空格也包含在內),%*s 表示匹配的字串但忽略該資料(可以理解佔了個位置)。&age 這裡需要將基本型別的記憶體地址引入。

使用 sscanf 轉換資料型別

#include <stdio.h>
int main() {
    char * str="100";
    int a;
    sscanf(str,"%d",&a);
    printf("轉換後:%d",a);
    return 0;
}

使用 atof 將字串轉為 double

#include <stdio.h>
#include <stdlib.h>
int main() {
    double result=atof("3.14");
    printf("轉換後:%f",result);
    return 0;
}

4、字串比較

== 用來比較兩個變數的記憶體地址,不能比較字串的值。使用 strcmp 比較兩個字串的值,兩個字串的值相等返回 0。

#include <stdio.h>
#include <string.h>
int main() {
    char * str1="hello";
    char str2[]="hello";
    if(strcmp(str1,str2)==0){
        puts("兩個字串的值相等");
    }else{
        puts("兩個字串的值不相等");
    }
    return 0;
}

5、字串的擷取

strchr:從指定字元開始擷取

#include <stdio.h>
#include <string.h>
int main() {
    char * str="helloworld";
    char * result=strchr(str,'w');
    puts(result);
    return 0;
}

strrchr:從最後一個指定字元開始擷取

#include <stdio.h>
#include <string.h>
int main() {
    char * str="helloworld";
    char * result=strrchr(str,'l');
    puts(result);
    return 0;
}

strstr:從指定字串開始擷取

#include <stdio.h>
#include <string.h>
int main() {
    char * str="helloworld";
    char * result=strstr(str,"wo");
    puts(result);
    return 0;
}

區分大小寫

strncpy:從字串中複製指定長度字元

#include <stdio.h>
#include <string.h>
int main() {
    char * str="helloworld";
    char dest[10];
    strncpy(dest,str,5);
    puts(dest);
    return 0;
}

使用指標從指定位置開始擷取

#include <stdio.h>
#include <string.h>
int main() {
    char * str="helloworld";
    char * str1=str+5;
    puts(str1);
    return 0;
}

可以先利用指標操作從某個位置開始擷取,然後利用 strncpy 擷取指定個。

六、函式

宣告和呼叫的方式和java很相似

1、main 函式的引數

main 函式的引數,也是程式執行時的引數

宣告時需指定:1.引數的長度,2.引數陣列

#include <stdio.h>
int main(int argc,char ** argv) {
    printf("引數的個數:%d\n",argc);
    for (int i = 0; i < argc; ++i) {
        printf("引數 %d 的值:%s\n",i,argv[i]);
    }
    return 0;
}

2、可變引數

#include <stdio.h>
#include <stdarg.h>
void testarg(int n,...){
    printf("引數的個數:%d\n",n);
    va_list args;
    va_start(args,n);
    for (int i = 0; i < n; ++i) {
        int temp=va_arg(args,int);
        printf("引數:%d\n",temp);
    }
    va_end(args);
}
int main(int argc,char ** argv) {
    testarg(2,10,55);
    return 0;
}

函式設定可變引數時需指定引數個數形參 int n ,和 ...(任意個數的引數)。需引入標頭檔案 stdarg.h,va_list 型別儲存有關變數引數的資訊,va_start 初始化變數引數列表,va_arg 取出引數的值,va_end 結束使用變數引數列表

七、預處理

1、預設常量

除了可以用 #define 定義一個常量,還可以在編譯引數中指定預設常量的值

編寫沒有宣告常量但使用了常量的程式碼

#include <stdio.h>

int main() {
    printf("The num is %d\n",THE_NUM);
    return 0;
}

使用命令進行編譯和執行

gcc -o test main.c -DTHE_NUM=4
test.exe

-o 引數指定編譯後可執行檔案的檔名,-D 引數後面直接跟常量名和值(沒有空格)

2、編譯條件針對不同平臺編譯

1.編寫程式碼

#include <stdio.h>

#define WIN 1
#define LINUX 2
#define MAC 3

int main() {
#if PLATFORM==WIN
    printf("Hello Windows\n");
#elif PLATFORM==LINUX
    printf("Hello Linux\n");
#elif PLATFORM==MAC
    printf("Hello MAC\n");
#else
    printf("Unknow platform\n");
#endif
    return 0;
}

if、#elif、#else、#endif 這些都是編譯條件,用於決定程式碼塊是否編譯

2.編譯時指定條件

//編譯原始碼
gcc -o test main.c -DPLATFORM=1
//執行
test.exe

3、標頭檔案重複引入

1、演示重複引入

1.建立a.h

#include "b.h"

2.建立b.h

#include "a.h"

3.引入a.h

#include <stdio.h>
#include "a.h"
int main() {

    return 0;
}

2、防止標頭檔案重複引入

修改a.h

#ifndef A_H
#define A_H
#include "b.h"
#endif

修改b.h

#ifndef B_H
#define B_H
#include "a.h"
#endif

通過標記一個常量來判斷是否重複引入,一旦擁有這個常量將不再引入

八、指標

宣告一個變數後,系統會為這個變數分配記憶體空間,每個變數都有一個記憶體地址,相當於旅館的房間號。

我們要獲取變數中的值需要通過記憶體地址來找到對應的值,但是還有另一種情況是這個變數中存放的是另個一變數的記憶體地址。這就好像你通過房間號找到這個房間,發現在這個房間裡面是另一個房間的房間號。

所以,一個變數的地址就成為該變數的指標

宣告指標並指向一個變數的地址

#include <stdio.h>
int main() {
    int *a,*b;
    int i=10;
    a=&i;
    b=&i;
    printf("a的值為:%d\n",*a);
    printf("b的值為:%d\n",*b);
    i=99;
    printf("a的值為:%d\n",*a);
    printf("b的值為:%d\n",*b);
    return 0;
}

C語言用*表示指標,為指標賦值時 & 取出變數的地址。(指標a和指標b指向同一個地址,修改i後指標中的也會改變,這個很類似Java和C#中的引用型別)

指標的大小

#include <stdio.h>
int main() {
    int *a;
    printf("size is %ld\n", sizeof(a));
    return 0;
}

64位系統中為8位元組,32位中為4位元組

1、函式指標

有參無返回值的函式指標

#include <stdio.h>

void hello(int a,char* c){
    printf("hello\n");
}
int main() {
    void(*fp)(int,char*)=&hello;
    fp(1,"");
    return 0;
}

有參有返回值的函式指標

#include <stdio.h>

int test(int a){
    printf("hello:%d\n",a);
    return a;
}
int main() {
    int(*fp1)(int)=&test;
    int result=fp1(11);
    printf("返回值:%d\n",result);
    return 0;
}

給函式指標設定類型別名

#include <stdio.h>

void hello(){
    printf("Hello\n");
}
typedef void(*SimpleHello)();
int main() {
    SimpleHello h1=&hello;
    h1();
    SimpleHello h2=&hello;
    h2();
    return 0;
}

重複定義時可以簡化程式碼

2、無型別指標

無型別指標可以代表所有型別的資料

無型別指標存放字串

#include <stdio.h>

int main() {
    void *test="Hello";
    printf("%s\n",test);
    return 0;
}

九、結構體與共同體

1、結構體

主要用結構體描述屬性

定義使用結構體

#include <stdio.h>
struct File{
    char* name;
    int size;
};
int main() {
    struct File file;
    file.name="a.txt";
    file.size=10;
    printf("檔案 %s 的大小為 %d\n",file.name,file.size);
    struct File file1={"a.jpg",100};
    printf("檔案 %s 的大小為 %d\n",file1.name,file1.size);
    return 0;
}

結構體也可以像陣列那樣初始化(C#中也有結構,兩者比較像可以對比記憶)

定義結構體型別簡化程式碼

#include <stdio.h>
typedef struct _File{
    char* name;
    int size;
}File;
int main() {
    File file;
    file.name="a.txt";
    file.size=10;
    printf("檔案 %s 的大小為 %d\n",file.name,file.size);
    File file1={"a.jpg",100};
    printf("檔案 %s 的大小為 %d\n",file1.name,file1.size);
    return 0;
}

2、結構體的記憶體對齊

結構體所佔大小如何計算的?結構的大小由各變數與位元組最大變數對齊後相加得出。

如圖所示,三種情況:

1.當定義第一個變數大小為1位元組,第二個變數比第一個變數大時,第一個變數要對齊大的變數,第一個變數填充為2位元組,結構體大小為4位元組。

2.前兩變數加起來不足對齊第三個變數的大小,所以填充2位元組對齊,結構體大小為8位元組。

3.前4個變數加起來剛好對齊第5個變數的大小,無須對齊。最後一個變數沒有對齊則填充2個位元組對齊第5個變數,所以結構體大小為12位元組。

#include <stdio.h>

typedef struct _Data{
    uint8_t a;//1
    uint8_t b;//1
    uint8_t c;//1
    uint8_t d;//1
    uint32_t e;//4
    uint8_t f;//1
}Data;
int main() {
    printf("size is %d\n", sizeof(Data));
    return 0;
}

此時結果為12

3、結構體指標

沒有使用指標前

#include <stdio.h>
typedef struct _Dog{
    char * color;
    int age;
}Dog;
int main() {
    Dog dog1={"紅色",2};
    Dog dog2=dog1;
    dog1.age=3;
    printf("(dog1 color is %s,age is %d),(dog2 color is %s,age is %d)\n"
        ,dog1.color,dog1.age,dog2.color,dog2.age
    );
    return 0;
}

沒有使用之前將dog1賦值給dog2相當於複製一個副本

使用結構體指標

#include <stdio.h>

typedef struct _Dog {
    char *color;
    int age;
} Dog;

int main() {
    Dog dog1 = {"紅色", 2};
    Dog *dog2 = &dog1;
    dog1.age = 3;
    printf("(dog1 color is %s,age is %d),(dog2 color is %s,age is %d)\n",
            dog1.color, dog1.age, dog2->color, dog2->age);
    return 0;
}

指標操作用法和之前一樣,需要注意的地方是:結構體指標,獲取結構體的值時需要使用 -> ,否則將編譯錯誤

通過函式建立和銷燬結構體指標

#include <stdio.h>
#include <stdlib.h>

typedef struct _Dog {
    char *color;
    int age;
} Dog;
Dog* createDog(char* color,int age){
    Dog* dog=malloc(sizeof(Dog));
    dog->color=color;
    dog->age=age;
    return dog;
}
void deleteDog(Dog* dog){
    free(dog);
}

int main() {
    Dog* dog1=createDog("紅色",3);
    printf("(dog1 color is %s,age is %d)\n",
            dog1->color, dog1->age);
    deleteDog(dog1);
    return 0;
}

malloc函式分配記憶體,使用完畢後用free函式釋放記憶體(釋放後的記憶體的值不可預期何時修改,free函式呼叫後別的程式就可以使用這塊空間了)

3、共同體

共同體的特點:共同體中宣告的所有變數共用一塊空間

宣告及使用共同體

#include <stdio.h>
typedef union _BaseData{
    char ch;
    uint8_t ch_num;
}BaseData;
int main() {
    BaseData data;
    data.ch='A';
    printf("ch_num is %d size is %d\n",data.ch_num, sizeof(BaseData));
    return 0;
}

變數 ch_num 和 ch 共用同一記憶體地址,大小為 1,共同體做型別轉換比較方便。

實現顏色的操作

#include <stdio.h>
//定義顏色結構體
typedef struct _ColorARGB{
    uint8_t blue;
    uint8_t green;
    uint8_t red;
    uint8_t alpha;
}ColorARGB;
//定義顏色共同體
typedef union _Color{
    uint32_t color;
    ColorARGB colorARGB;
}Color;
int main() {
    Color c;
    c.color=0xFFAADD00;
    printf("red:%X",c.colorARGB.red);
    return 0;
}

存入的資料是以小端模式,所以定義顏色結構體時的通道的順序和賦值的顏色順序相反。結合結構體與共同體實現(巧妙)

十、檔案操作

fopen函式:以指定檔名和模式獲取一個流的FILE指標。

支援的模式

模式 說明
"r" 讀:檔案必須存在
"w" 寫:建立一個檔案,如果已經存在則覆蓋
"a" 追加:向檔案末尾追加內容,如果檔案不存在則建立該檔案
"r+" 可做讀取和修改的操作,檔案必須存在

寫檔案

//建立檔案指標
FILE * file=fopen("a.txt","w");
if (file){
    //寫入內容
    fputs("HelloWorld",file);
    //關閉
    fclose(file);
}else{
    puts("檔案操作失敗!");
}

讀檔案

#include <stdio.h>
int main() {
    FILE * file=fopen("a.txt","r");
    if (file){
        char ch;
        while(1){
            ch=fgetc(file);
            if(ch!=EOF){
                printf("%c\n",ch);
            }else{
                break;
            }
        }
        fclose(file);
    }
    return 0;
}