博客 / 詳情

返回

小凱15天快速講完c語言-簡單學習第七課

前言

看到這篇博客的同學們,到今天為止,我們的c語言初級部分講解就結束了(可能有的同學好奇我的標題不是寫的15天麼,這才七天,哈哈,因為我們接下來就要開始進入c++的世界了,算是c語言的進階,我今天整理髮布的曾經自學的筆記相對有些複雜,涉及指針高級運算,今天的內容不求掌握,只求簡單理解就好,即使沒懂,也沒關係啦,樓主純手動碼字不易,還望珍惜。歡迎關注,多和我交流。

0. 複習

0.1 結構體

是一種複合數據類型,可以將多個不同類型得變量給捏在一起。一般用於代表某一個整體得信息。
比如:學生信息有學生姓名,年齡,學號.... 貪吃蛇的 速度 血量 長度.....
語法:

struct 類型名
{
  字段1類型   字段1名字; //字段也叫做成員
  字段2類型   字段2名字;
    .....;
};
struct  類型名  變量名 = {初始值};
C語言中定義結構體變量的時候,需要加上struct.c++不需要
C語言的程序員為了不寫這個struct,有了一種類型定義的寫法
typedef struct _類型名
{
  字段1類型   字段1名字; //字段也叫做成員
  字段2類型   字段2名字;
    .....;
}類型名,*P類型名;
typedef struct _STUDENT
{
  //
}STUDENT,*PSTUDENT;

使用結構體的時候,按照成員本身的類型去使用。

0.2 聯合體

和結構體語法是類型的,區別在於聯合的所有成員是共享內存的。
比較適合用在 成員互斥的情況下(一個有效,其他的就都是無效的)

0.3 類型定義

typedef int INT; //INT 就是int的別名

0.4 堆空間

申請:malloc
釋放:free
設置內存中的值:memset
拷貝內存:memcpy
一些概念:
懸空指針:釋放之後,沒有被置為nullptr的指針
野指針:沒有初始化的指針
懸空指針和野指針都指向無效區域。
正在運行的程序,有5個內存區域:
靜態數據區:全局變量,static局部變量所在的區域
常量區:字符串常量所在的區域
代碼區:代碼所在的區域
棧區:局部變量和函數的參數都在棧區
堆區:malloc 申請的空間,是堆區的。
圖片.png
作用域:變量起作用的一個範圍
生存期:變量存在的一個時期
局部變量,參數:進入函數,有效,此時在棧區創建出來。離開函數,失效,此時自動銷燬。
靜態局部變量:進入函數之前就被創建。但是在函數外面是不能使用的。離開函數,也不會被銷燬。
堆區:申請就存在,釋放就銷燬

1. 指針進階

1.1 指針的算術運算

1.1.1 指針+(-) 整數

#include <stdio.h>
int main()
{
    int a = 100;
    int* p = NULL;
    p = (int*)100;
    //1. 一個地址本質來説也是一個數字
    //但是地址一般都是由&得到的,或者malloc函數返回的
    //如果我們隨便寫個地址,這個地址通常都是不能訪問
    //2. 做加法運算
    //a+n 一個整型做加法運算,為了得到一個算術結果
    //p+n 指針做加法運算,是為了得到偏移為n的元素的位置。
    //有了這個位置,就可以通過*得到偏移為n的位置的數據
    printf("%d\n", a );
    printf("%d\n", a+2);
    printf("%d\n", p);
    printf("%d\n", p + 2);
    //3. 這個特性有什麼用呢???
    int arr[10] = { 4,5,20,40,10,6,7,8,9,10 };
    p = arr;//能賦值,説明類型相似
    for (int i = 0; i < 10; i++)
    {
        printf("%x ", p + i);
        printf("%d \n", *(p + i));
    }
    //4. double* char*  short*  結構體*
    typedef struct _TEST
    {
        int a;
        double b;
        int c;
    }TEST,*PTEST;
    double* p2 = (double*)100;
    char* p3 = (char*)100;
    short* p4 = (short*)100;
    //下面兩種寫法等價的
    PTEST p5 = (PTEST)100;
    TEST* p6 = (TEST * )100;
    printf("%d\n", p2);
    printf("%d\n", p3);
    printf("%d\n", p4);
    printf("%d\n", p5);
    printf("%d\n", p6);

    printf("%d\n", p2+1);//108
    printf("%d\n", p3+1);//101
    printf("%d\n", p4+1);//102
    printf("%d\n", p5+1);//124
    printf("%d\n", p6+1);//124



    return 0;
}

1.1.2 指針- 指針(大概瞭解即可)

要求:兩個指針的類型必須是一致的。得到的結果是兩個地址之間能夠存儲下多少個此類型
圖片.png

1.2 指針和一維數組

指針和一維數組有非常多的相同點:

#include <stdio.h>
int main()
{
    //1. 對於不相似的類型,C語言是不讓直接賦值的
    int nNum1 = 100;
    short sNum1 = 0;
    sNum1 = nNum1;
    int* p1 = NULL;
    short* p2 = NULL;
    p1 = &nNum1;
    p2 = &sNum1;
    //類型非常不同,不能直接賦值的
    //p = nNum1;不行
    //short*和int*也是不能直接賦值的
    //運算規則不同
    //p1 = p2;
    //指針和數組可以直接賦值
    int arr[10] = { 4,5,20,40,10,6,7,8,9,10 };
    int* p = arr;//能賦值,説明類型相似
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
        printf("%d ", *(arr + i));
        printf("%d ", arr[i]);
        printf("%d \n", p[i]);
    }

    return 0;
}

圖片.png
是否就可以説 指針就是數組呢???
1.指針是一個變量,可以指向其他位置
2.數組名是數組的起始地址,是一個常量,不能改變的
從sizeof的角度説,他倆也不同。
在32位,任何一個指針,都是四個字節的變量。

1.3 一級指針的使用場景

1.3.1 通過函數去修改外部的變量

#include <stdio.h>
void Test(int* a)
{
    *a = 500;
}

int main()
{
    int nNum = 100;
    Test(&nNum);
    printf("%d", nNum);
    return 0;
}

1.3.2 將數組作為一個參數進行傳遞的時候

#include <stdio.h>

//         int* Test
//         int Test[]
//         int Test[100]
int GetAdd(int Test[10])
{
    int s = 0;
    printf("%d\n",sizeof(Test));
    for (int i = 0; i < 10; i++)
    {
        s += Test[i];
    }
    return s;
}
int main()
{
    //假如有一個數組
    int arrTest[10] = { 4,3,5,7,8,1,2,9,0,10 };
    //通過函數求所有元素的和
    //數組名是一個地址,接收地址的只能是指針
    printf("%d\n", sizeof(arrTest));
    int n = GetAdd(arrTest);
    printf("%d", n);
    return 0;
}

1.3.3 使用堆空間的時候

#include <stdio.h>
#include <stdlib.h>
//         int* Test
//         int Test[]
//         int Test[100]
int GetAdd(int Test[10], int nCount)
{
    int s = 0;
    printf("%d\n", sizeof(Test));
    for (int i = 0; i < nCount; i++)
    {
        s += Test[i];
    }
    return s;
}
int main()
{
    int* p = (int*)malloc(5 * sizeof(int));
    for (int i = 0; i < 5; i++)
    {
        printf("請輸入一個數據:");
        scanf_s("%d", &p[i]);
        //scanf_s("%d", p+i);
    }
    int n = GetAdd(p, 5);
    printf("和為%d", n);

    return 0;
}

1.4 指針和二維數組(對初學者來説難度很大,儘量理解就好,沒有理解也沒有關係,不必氣餒)

#include <stdio.h>

int main()
{
    int arrTest1[3][4] =
    {   1,2,3,4,
        5,6,7,8,
        9,10,11,12 };
    int arrTest2[10][4] =
    { 1,2,3,4,5,6,7,8,9,10,11,12 };
    int arrTest3[3][5] =
    { 1,2,3,4,5,6,7,8,9,10,11,12 };
    //printf("%p\n", arrTest1);
    //二維數組名也是地址
    //但是類型並非是普通的指針
    //int* p1 = arrTest1;
    //類型和  【數組指針】  相似
    int(*p1)[4] = NULL;
    p1 = arrTest1;//賦值成功,類型確實相似
    p1 = arrTest2;//這個也能成功,就是列數對上了就行
    //p = arrTest3; 不能成功
    int(*p2)[5] = arrTest3;
    //數組指針有什麼特點:
    //數組指針+1是加了一排
    int(*p3)[4] = ( int(*)[4] )100;
    //printf("%d ", p3);
    //printf("%d", p3 + 1);
    //數組指針和二維數組加法規則一致
    p1 = arrTest1;
    printf("%p\n", p1);
    printf("%p\n", p1+1);
    printf("%p\n", p1 + 2);
    printf("%p\n", arrTest1);
    printf("%p\n", arrTest1 + 1);
    printf("%p\n", arrTest1 + 2);
    //解一次引用
    //對於數組指針和二維數組而言
    //解一次引用,還是那個地址值不變
    //但是 類型發生了變化
    printf("------------------\n");
    printf("%p\n", p1);      //數組指針類型
    printf("%p\n", *p1);     //一級指針類型,指向的是下標為0的那一排
    printf("%p\n", p1 + 1);  //數組指針類型
    printf("%p\n", *(p1 + 1)); //一級指針類型,指向的是下標為1的那一排
    printf("------------------\n");
    printf("%p\n", arrTest1);
    printf("%p\n", *arrTest1);
    printf("%p\n", arrTest1 + 1);
    printf("%p\n", *(arrTest1 + 1));
    //區別
    printf("------------------\n");
    printf("%p\n", p1+1);
    printf("%p\n", p1 + 1+1);//再往下找一排+16
    printf("%p\n", *(p1 + 1) + 1);//在本排中找下一個元素+4

    printf("%p\n", *(*(p1 + 1) + 1));
    return 0;
}

代碼有點冗長和繞,一張圖理解總結下:
圖片.png

    //遍歷二維數組
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%d", p1[i][j]);
            printf("%d", *(p1[i]+j));
            printf("%d", *(*(p1+i)+j));
            printf("%d", (*(p1 + i))[j]);

            printf("%d", arrTest1[i][j]);
            printf("%d", *(arrTest1[i] + j));
            printf("%d", *(*(arrTest1 + i) + j));
            printf("%d", (*(arrTest1 + i))[j]);
        }
    }

1.5 使用場景

1.5.1 傳參

1.5.2 使用堆空間

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
//         int (*test)[4]
//         int test[][4]
//         int test[8][4]
int GetAdd(int(*test)[4],int nRowCount)
{
    printf("%d", sizeof(test));
    int s = 0;
    for (int i = 0; i < nRowCount; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            s += test[i][j];
        }
    }
    return s;
}
int main()
{
    //1. 數組傳參
    int arr[3][4] = { 1,2,3,4,
        5,6,7,8,
        9,10,11,12 };
    printf("%d", sizeof(arr));
    GetAdd(arr,3);
    //2. 使用堆空間,可以把堆空間當數組來用
    int(*p)[4] = (int(*)[4])malloc(5 * 4 * sizeof(int));
    memset(p, 0, 5 * 4 * sizeof(int));
    p[1][1] = 10;
    GetAdd(p, 5);
    return 0;
}

1.6 數組指針和指針數組

// 數組指針 是指針
// 指針數組 是數組

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

int main()
{
    //數組指針是一個指針
    //可以把一塊內存區域,按照二維數組的訪問去使用
    int(*p1)[4] = nullptr;
    int arrTest[5][4] = { 1,2,3 };
    p1 = arrTest;
    //指針數組
    int nNum = 0;
    int arr[3] = { 1,2,3 };
    int* p2[4] = { NULL,NULL,NULL,NULL };
    p2[0] = (int*)malloc(5 * sizeof(int));
    p2[1] = &nNum;
    p2[2] = arr;
    p2[3] = (int*)malloc(6 * sizeof(int));
    //雖然兩個語法是不同的概念
    //但是在使用的時候有相似性
    arrTest[1][1] = 100;
    //這個代表 2號指針 指向的位置 裏面下標為1的地方
    p2[2][1] = 20;



    return 0;
}

字符類型

//字符串
    char arr1[3][20] = { "xiaoming","xiaobai","xiahui" };
    printf("%s", arr1[0]);
    printf("%s", arr1[1]);
    arr1[0][1] = 'a';
    const char* arr2[3] = { "xiaoming","xiaobai","xiahui" };
    printf("%s", arr2[0]);
    printf("%s", arr2[1]);
    //arr2[0][1] = 'a';

1.7 結構體指針

int main()
{
    TEST stc = {10,2.5,20};
    PTEST pstc = NULL;
    pstc = &stc;
    printf("%d %d\n", pstc->a, stc.a);
    printf("%lf %lf\n", pstc->b, stc.b);
    printf("%d %d\n", pstc->c, stc.c);

    pstc = (PTEST)malloc(sizeof(TEST) * 3);
    memset(pstc, 0, sizeof(TEST) * 3);
    for (int i = 0; i < 3; i++)
    {
        (pstc + i)->a = i * 10+1;
        pstc[i].b = 3.3;
    }
    return 0;
}

1.8 二級指針

圖片.png

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
void Fun(int** p, int n)
{
    *p = (int*)malloc(n * sizeof(int));
    memset(*p, 0, sizeof(int) * n);
}

int main()
{
    int a = 100;
    int* p1 = &a;
    //二級指針
    int** p2 = &p1;

    //應用,假如説我希望在一個函數中申請堆空間,出了函數
    //這個堆空間還能使用
    int* p3 = NULL;
    Fun(&p3, 5);


}

2. 文件操作

2.1 文件的簡單分類

1.文本文件(使用記事本能夠直接打開並查看顯示的問題),本質上存儲的是文本的編碼
2.二進制文件(電影,音樂,圖片,通常是給特定的軟件去使用的)

2.2 關於路徑的問題

我們在windows中,去定位一個文件,通常都是使用路徑的,去找到某一個文件
路徑有兩種形式:
1.絕對路徑
D:\Test\abc.txt
2.相對路徑
是從當前目錄出發,去尋找某一個文件
. 代表當前目錄
.. 代表上一級目錄

例子:
.\abc.txt
..\test\abc.txt
什麼是當前目錄:
1.當我們F5 調試運行的時候,代碼所在的目錄就是當前目錄
2.當我們直接運行程序的時候,exe文件所在的目錄就是當前目錄

2.3 文件操作的基本步驟

1.打開文件 fopen_s
圖片.png

2.讀寫文件

  從文件中讀取數據              往文件中寫入數據
       fgetc                                  fputc
       fgets                                  fputs
       fscanf_s                             fprintf
       fread                                  fwrite

文件讀取寫入函數-都是字符

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;
    ////1. 打開一個文件,以w的方式,文件不存在就會創建一個
    ////這裏的文件指針,就是代表一個打開的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "w");

    ////2. 寫入內容
    //// 2.1 fputc
    ////fputc('A', pFile);
    ////fputc('B', pFile);
    ////fputc('C', pFile);
    //// 2.2 fputs
    //// fputs("hello world", pFile);
    //// 2.3 fprintf
    //fprintf(pFile, "%d %d %lf", 10, 20, 8.55);
    ////3. 關閉文件
    //fclose(pFile);

    //讀取
    //1. 打開文件
    pFile = nullptr;
    fopen_s(&pFile, "..\\debug\\abc.txt", "r");
    //2. 讀取文件
    //2.1 fgetc
    //char cCh = 0;
    //while (cCh!=EOF)
    //{
    //    cCh = fgetc(pFile);
    //    printf("%c", cCh);
    //}
    //2.2 fgets
    //char buf[20] = {};
    //fgets(buf,20,pFile);
    //2.3 fscanf_s
    int a = 0;
    int b = 0;
    double c = 0;
    fscanf_s(pFile, "%d %d %lf", &a, &b, &c);
    //3. 關閉文件
    fclose(pFile);
}

文件讀取寫入函數- 內存讀寫

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;

    ////1. 打開一個文件,以w的方式,文件不存在就會創建一個
    ////這裏的文件指針,就是代表一個打開的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "w");

    ////2. 寫入內容
    //int arr[7] = { 10,20,30,40,50,60,70 };
    ////這個函數,是將內存中數據寫入到文件中
    ////寫入了4*7  也就是28個字節
    //fwrite(arr, 4, 7, pFile);
    ////3. 關閉文件
    //fclose(pFile);

    ////讀取
    ////1. 打開文件
    pFile = nullptr;
    fopen_s(&pFile, "..\\debug\\abc.txt", "r");
    ////2. 讀取文件
    int arr[7] = { };
    fread(arr, 4, 7, pFile);
    ////3. 關閉文件
    //fclose(pFile);
}

3.關閉文件-fclose

2.4 打開文件的模式 加不加 b的問題

圖片.png
所有的打開的模式,都可以添加一個b,比如:wb rb ab w+b r+b a+b。
有了這個b的模式,叫做二進制模式,沒有b的就是文本模式。
文本模式,寫入的時候,遇到了0A 就會轉為0D 0A 。讀取的時候遇到了 0D 0A 就會轉換為0A讀取進來。
文本模式,遇到了0A轉換為0D 0A 如下:
圖片.png
因為在windows平台,\n的asc碼 是 0A ,但是隻有\n不能換行,要顯示換行 需要是 \r \n 就是0D 0A。
如果是二進制模式,那麼就不會轉換
圖片.png
唯一的區別就在於會進行微小的轉換。
一定要成對使用,寫入的時候 加了b ,讀取的時候也應該加

4.補充函數:

                  fseek
                  ftell

有一個文件讀寫位置。在讀取或者寫入時會自動設置這個位置。
比如一個文件有100個字節。剛剛打開的時候,位置是在0這個地方,讀取了3個字節,位置就會往後調整3個字節。
fseek 的作用:設置讀寫位置

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;
    ////1. 打開一個文件,以w的方式,文件不存在就會創建一個
    ////這裏的文件指針,就是代表一個打開的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "wb");

    ////2. 寫入內容
    //fputs("Hello world", pFile);
    ////3. 關閉文件
    //fclose(pFile);
    fopen_s(&pFile, "..\\debug\\abc.txt", "rb");
    char cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    //下面這個函數,能夠調整讀寫的位置
    //SEEK_END 以結尾為一個錨點
    //SEEK_SET 以開始為錨點
    //SEEK_CUR 以現在的位置為錨點
    fseek(pFile, 0, SEEK_SET);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
}

ftell的作用:是查看當前的讀寫的位置在哪??

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;
    ////1. 打開一個文件,以w的方式,文件不存在就會創建一個
    ////這裏的文件指針,就是代表一個打開的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "wb");

    ////2. 寫入內容
    //fputs("Hello world", pFile);
    ////3. 關閉文件
    //fclose(pFile);
    fopen_s(&pFile, "..\\debug\\abc.txt", "rb");
    char cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    //fseek這個函數,能夠調整讀寫的位置
    //SEEK_END 以結尾為一個錨點
    //SEEK_SET 以開始為錨點
    //SEEK_CUR 以現在的位置為錨點
    fseek(pFile, 0, SEEK_SET);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    //ftell 告訴當前的文件讀寫位置在哪
    int nSize = ftell(pFile);
    cCh = fgetc(pFile);
    nSize = ftell(pFile);
    //fseek和ftell配合可以獲取文件大小
    fseek(pFile, 0, SEEK_END);
    nSize = ftell(pFile);
    printf("當前文件大小就是%d字節", nSize);


}

注意:
使用這些函數的時候,最好成對使用。
寫入的時候如果加了 b,讀取的時候也一定要加上。
在一次的打開和關閉之間,只進行一次讀寫操作

作為基礎部分的結束,奉上我自己曾經總結的c語言經典題供進一步強化

(持續更新,已更新至8月25日)C語言經典題集合

user avatar _6375bdd01b3a4 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.