C語言存儲類別、鏈接和內(nèi)存管理
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "diceroll.h" // 包含的頭文件如果是C標準庫的,需要使用<>告訴編譯器去標準庫中查找,如果是用戶定義的,需要使用""告訴編譯器去IDE指定的用戶存放頭文件的區(qū)域查找
int mode; // 將變量定義在函數(shù)外面,變量具有文件作用域,變量mode對當(dāng)前翻譯單元(當(dāng)前文件和include包含的所有文件的總和稱為翻譯單元)內(nèi),當(dāng)前聲明語句之后的所有函數(shù)可見
double distance; // 變量distance可以描述為文件作用域,靜態(tài)存儲期(在編譯時分配內(nèi)存,并在程序運行過程中一直保留這塊內(nèi)存),外部鏈接(在當(dāng)前程序的其他翻譯單元中可以引用該變量,翻譯單元即由.c源文件和文件中包含的頭文件等的總和,程序如果由多個源文件組成會形成多個翻譯單元),靜態(tài)變量如果沒有顯式初始化則會將所有位初始化為0(在某些硬件系統(tǒng)中浮點數(shù)的0不是所有位都為0)
static double fuel; // 對文件作用域的變量使用static修飾,將變量定義為內(nèi)部鏈接,內(nèi)部鏈接只對當(dāng)前翻譯單元可見,其他翻譯單元不能引用
extern int sets; // 使用extern,引用式聲明,告訴編譯器去其他地方(包括當(dāng)前文件的上下文)尋找sets,這里sets的定義式聲明在別的翻譯單元中,當(dāng)前翻譯單元想要使用同一個變量則必須要extern引用
void critic(void); // 函數(shù)原型默認extern引用,函數(shù)也具有文件作用域
void critic2(int* ptr); //函數(shù)原型作用域用于函數(shù)原型中的形參/變量名,作用域范圍從定義處到原型聲明結(jié)束,所以除了變長數(shù)組之外,形參名可以省略,簡寫為void critic2(int*); 函數(shù)定義時必須有形參名,且函數(shù)定義的形參名可以和函數(shù)原型中的不同
void set_mode(int p_mode);
void get_info(void);
void show_info(void);
int invoke_count(void);
void random_array(void);
void array_sort(int* arr, int len);
void test_random(void);
static void dice_game(void); // 將函數(shù)聲明為內(nèi)部鏈接,文件/翻譯單元私有
int* dynamic_make_array(int size, int init);
void dynamic_show_array(int* arr, int size);
void words(void);
void others(void);
int main(void)
{
???? words();
???? return 0; //C規(guī)定main()函數(shù)的return 0和exit(EXIT_SUCCESS)作用相同,0代表成功運行
}
void critic(void)
{
???? auto int units = 0; // 一般在花括號{}內(nèi)聲明的變量和函數(shù)形參內(nèi)的變量都默認auto,自動存儲期,指程序在執(zhí)行到當(dāng)前函數(shù)才會分配內(nèi)存給變量(存儲在棧中),并且在函數(shù)結(jié)束時釋放內(nèi)存,當(dāng)前變量的標識符為units,程序通過該標識符訪問內(nèi)存塊,在C語言中這個內(nèi)存塊稱為對象(不同于面向?qū)ο笳Z言的對象),變量units可以被描述為自動存儲期、塊作用域(花括號{}內(nèi)的區(qū)域被稱為塊)、無鏈接(塊作用域以外不可見,除了傳參/返回之外無法引用)
???? //雖然units的內(nèi)存在程序執(zhí)行到當(dāng)前函數(shù)時就已分配,但只對當(dāng)前塊內(nèi)在units的聲明語句下面的區(qū)域可見,自動存儲器的變量在聲明時如果不手動進行初始化則該內(nèi)存塊仍然保留著之前該區(qū)域的垃圾值
???? printf("How many pounds to a firkin of butter?\n");
???? scanf("%d", &units);
???? while (units!=56)
???? {
???? ????critic2(&units);
???? }
???? printf("You must have looked it up!\n");
}
void critic2(int* ptr) { // 函數(shù)頭的普通變量也是自動存儲期、塊作用域、無鏈接
???? printf("No luck, my friend. Try again.\n");
???? scanf("%d", ptr);
} // 離開critic2函數(shù)會釋放變量ptr的內(nèi)存,即&ptr地址的內(nèi)存塊,這不影響主調(diào)函數(shù)的&units
void set_mode(int p_mode) {
???? // 如果形參也命名為mode會蓋住前面定義的靜態(tài)變量mode,使靜態(tài)變量mode對當(dāng)前函數(shù)不可見
???? extern int mode; // 引用式聲明,靜態(tài)變量mode對當(dāng)前翻譯單元所有函數(shù)可見,所以不需要這一句聲明也可以調(diào)用變量,引用式聲明不可以初始化,只有定義式聲明可以初始化
???? switch (p_mode)
???? {
???????? case 1:
???????????? mode = 1;
???????????? break;
???????? case 0:
???????????? mode = 0;
???????????? break;
???????? default:
???????????? printf("Invalid mode specified. Mode %s used.\n", mode ? "1(US)" : "0(metric)");
???? }
}
void get_info(void) {
???? printf("Enter distance traveled in %s:", mode ? "miles" : "kilometers");
???? scanf("%lf", &distance);
???? printf("Enter fuel consumed in %s:", mode ? "gallons" : "liters");
???? scanf("%lf", &fuel);
}
void show_info(void) {
???? if(mode)
???????? printf("Fuel consumption is %.2f miles per gallon.\n",distance / fuel);
???? else
???????? printf("Fuel consumption is %.2f liters per 100 km.\n", fuel / distance * 100);
}
int invoke_count(void) { // 記錄函數(shù)被調(diào)用的次數(shù)
???? static int invoke_c; // 聲明靜態(tài)存儲期的塊作用域無鏈接變量,靜態(tài)變量invoke_c會在編譯階段分配內(nèi)存,在程序運行時一直存在,所以實際函數(shù)調(diào)用時會跳過這個語句,靜態(tài)變量在程序一開始執(zhí)行就存在,沒有顯式初始化則初始化為0
???? invoke_c++; //靜態(tài)變量在程序運行時一直存在,所以該值會不斷遞增。塊作用域使其只對當(dāng)前函數(shù)可見,函數(shù)私有,無鏈接使其不能被extern引用
???? return invoke_c;
}
void random_array(void) {
???? int arr[100];
???? srand(time(0)); //time()函數(shù)在time.h頭文件中,傳入指向time_t類型的指針,返回當(dāng)前時間戳并將該值也存到傳入的指針指向的對象上,傳入NULL或0省略指針賦值,srand()接收unsigned int類型作為隨機數(shù)的種子,傳入time_t類型可能發(fā)生截斷
???? for (int i = 0; i < 100; i++) //for循環(huán)的()為函數(shù)的語句塊的子塊,在子塊中聲明的變量的作用域也為子塊,所以for循環(huán)結(jié)束之后會釋放i的內(nèi)存
???? { // for循環(huán)的循環(huán)體{}為for循環(huán)的()的子塊,如果在{}內(nèi)再聲明一個int i,會蓋住()中i,當(dāng)單次循環(huán)結(jié)束離開{}進入()判定時依然是以()中聲明的i來判定
???? ????arr[i] = rand() % 10 + 1; //rand()生成隨機數(shù),范圍0-RAND_MAX,%10是0-9,+1是1-10
???? }
???? array_sort(arr, 100);
???? for (int i = 0; i < 100; i++)
???? {
???? ????printf(" %d,", arr[i]);
???? }
???? putchar('\n');
}
void array_sort(int* arr, int len) { // 使用歸并排序
???? if (len<2)
???? return;
???? int mid = len / 2;
???? array_sort(arr, mid);
???? array_sort(arr+mid, len-mid);
???? int* ptr = (int*)malloc(len * sizeof(int)); // malloc(size_t size)函數(shù)動態(tài)分配內(nèi)存,即在程序運行中,在執(zhí)行到malloc()函數(shù)時根據(jù)傳參的大小分配內(nèi)存塊,返回內(nèi)存塊的地址,類型為void*即指向void的指針,該類型相當(dāng)于一個通用指針,通過(int*)強轉(zhuǎn),動態(tài)分配的內(nèi)存需要通過free()函數(shù)來釋放,不會自動釋放
???? // 通過len*sizeof(int)生成存放len個int元素的空間
???? // malloc()如果分配內(nèi)存失敗,會返回NULL
???? if (ptr==NULL)
???? {
???????? printf("動態(tài)分配失敗");
???????? exit(EXIT_FAILURE);
???? }
???? int r, l,i;
???? for ( i=l = 0,r=mid; l < mid&&r<len;)
???? ????ptr[i++] = arr[l] > arr[r] ? arr[l++] : arr[r++];
???? while (l<mid)
???? ????ptr[i++] = arr[l++];
???? while (r<len)
???? ????ptr[i++] = arr[r++];
???? for ( i = 0; i < len; i++)
???? ????arr[i] = ptr[i];
???? free(ptr); //free()只能釋放動態(tài)分配的內(nèi)存,自動存儲期和靜態(tài)存儲期都不行
}
void test_random(void) {
???? int count[11]={0}; // 只顯式初始化一個值,剩下的元素都初始化為0
???? for (int i = 0; i < 10; i++)
???? {
???????? srand(time(0)+i*i);
???????? for (int j = 0; j < 1000; j++)
???????? {
???????? ????count[rand() % 10 + 1]++; //count[1]對應(yīng)隨機數(shù)1,count[10]對應(yīng)隨機數(shù)10,每次出現(xiàn)哪個隨機數(shù)就把哪個計數(shù)器遞增
???????? }
???????? printf("loop %d:\t", i);
???????? for (int j = 1; j < 11; j++)
???????? {
???????????? printf("count[%d]=%d\t", j, count[j]);
???????????? count[j] = 0;
???????? }
???????? putchar('\n');
???? }
}
int roll_n_dice(int dice, int sides) { // 拋多個骰子,dice接收骰子的個數(shù),sides指定骰子有幾面
???? int n, sum;
???? sum = n = 0;
???? putchar('(');
???? for (int i = 0; i < dice; i++)
???? {
???????? n = rand() % sides + 1;
???????? printf("%d ", n);
???????? sum += n;
???? }
???? printf(") = %d\n", sum);
???? return sum;
}
void dice_game(void) {
???? int dice, sides;
???? fputs("Enter the number of sets(1-64); enter q to stop:", stdout);
???? while (scanf("%d",&sets)==1&&sets>0&&sets<65)
???? {
???????? srand(time(0));
???????? printf("How many sides(2-64) and how many dice(1-64)?");
???????? if (scanf("%d%d",&dice,&sides)!=2||dice<0||dice>64||sides<2||sides>64)
???????? {
???????? ????puts("invalid input. sides(2-64) dice(1-64).");
???????? }
???????? else
???????? {
???????????? printf("Here are %d sets of %d %d-sided throws.\n", sets, dice, sides);
???????????? for (int i = 0; i < sets; i++)
???????????? {
???????????? ????roll_n_dice(sides, dice);
???????????? }
???????? }
???????? fputs("How many sets(1-64)? Enter q to stop:", stdout);
???? }
}
int* dynamic_make_array(int size, int init) {
???? int* ptr = (int*)calloc(size, sizeof(int)); // calloc()函數(shù)也用于動態(tài)分配內(nèi)存,第一個參數(shù)指定元素的數(shù)量,第二個參數(shù)指定元素的大小,函數(shù)會將所有位初始化為0,返回void*需要強轉(zhuǎn)
???? if(ptr) //動態(tài)分配內(nèi)存如果失敗會返回NULL
???????? for (int i = 0; i < size; i++)
???????????? ptr[i] = init;
???? return ptr;
}
void dynamic_show_array(const int* arr, int size) {
???? // 指針arr是其指向?qū)ο蟮脑诋?dāng)前函數(shù)內(nèi)的初始方式,如果同時也是唯一的方式(即函數(shù)內(nèi)沒有用其他指針也指向該對象),可以使用restrict關(guān)鍵字修飾arr,restrict關(guān)鍵字用于編譯器優(yōu)化,告訴編譯器當(dāng)前聲明的指針是訪問數(shù)據(jù)對象唯一且初始的方式,restrict關(guān)鍵字只能用于指針
???? for (int i = 0; i < size; i++)
???? {
???????? printf("%d ", arr[i]);
???????? if (i % 8 == 7)
???????? putchar('\n');
???? }
}
void words(void) {
???? fputs("How many words do you wish to enter?", stdout);
???? int n;
???? if (scanf("%d", &n) == 1 && n > 0)
???? {
???????? char** words = (char**)malloc(n * sizeof(char*)); // 創(chuàng)建指向字符串的指針數(shù)組,可以想象成:char是字符,char*是字符串/字符數(shù)組,char**是字符串?dāng)?shù)組/指向字符串的指針數(shù)組,所以每個元素為指針大小,有n個元素
???????? if (words == NULL)
???????? ????goto end; // goto語句的標簽是函數(shù)作用域,即使一個標簽首次出現(xiàn)在函數(shù)的內(nèi)層塊中,它的作用域也延伸至整個函數(shù)
???????? printf("Enter %d words now:\n", n);
???????? char temp[20];
???????? for (int i = 0; i < n; i++)
???????? {
???????????? scanf("%19s", temp); // 最多讀19個字符,末尾留一位\0
???????????? words[i] = (char*)malloc((strlen(temp) + 1) * sizeof(char)); // 先存放到臨時數(shù)組
???????????? strcpy(words[i], temp);????//字符串賦值
???????? }
???????? puts("Here are your words:");
???????? for (int i = 0; i < n; i++)
???????? {
???????????? printf("%s ", words[i]);
???????????? free(words[i]); // 每個動態(tài)分配的內(nèi)存分別釋放
???????? }
???????? free(words); // 千萬記得釋放
???? }
????end:;????//goto的end標簽,對應(yīng)的語句為;空
}
void others(void) {
register int i; // register關(guān)鍵字,請求將聲明的變量儲存在CPU的寄存器中,與普通變量相比,寄存器變量訪問、處理的速度更快,無法獲得寄存器變量的地址,所以無法使用&取址,編譯器視情況決定是否將該變量儲存到寄存器中,即便儲存在了內(nèi)存中也不能使用&
const volatile char* ptr; //volatile易變的,volatile限定符告知計算機,代理(而不是變量所在的程序)可以改變該變量的值,通常被用于硬件地址以及在其他程序或同時運行的線程中共享數(shù)據(jù),編譯器會根據(jù)volatile改變優(yōu)化,const和volatile順序無所謂
//_Thread_local //以關(guān)鍵字 _Thread_local聲明具有線程存儲期的對象,每個線程都獲得該變量的私有備份,線程存儲期從被聲明時到線程結(jié)束一直存在
//_Atomic int hogs; // _Atomic原子類型變量,并發(fā)程序通過各種宏函數(shù)訪問原子類型,當(dāng)一個線程對一個原子類型的對象執(zhí)行原子操作時,其他線程不能訪問該對象
//static int j; int k; malloc(sizeof(int)); //靜態(tài)變量、自動變量、動態(tài)變量分別存儲在內(nèi)存中的三個不同的區(qū)域
int?arr1[10];
int?arr2[10]={1,2,3,4,5,6,7,8,9,0};
memcpy(arr1, arr2, sizeof(arr2)); //內(nèi)存字節(jié)拷貝,參數(shù)1、2和返回值(返回參數(shù)1)都是void*類型,參數(shù)3指定字節(jié)數(shù),參數(shù)1和參數(shù)2不應(yīng)重疊
memmove(arr1, arr2, sizeof(arr2)); //和memcpy區(qū)別是參數(shù)1、2可以重疊,使用緩沖區(qū)進行拷貝,src拷貝到緩沖區(qū)再拷貝到dest
/*存儲期分為靜態(tài)、自動、動態(tài)。靜態(tài)存儲期在程序開始執(zhí)行時分配內(nèi)存。自動存儲期在程序進入變量定義所在的塊時分配變量內(nèi)存,離開塊釋放內(nèi)存。動態(tài)存儲期在調(diào)用malloc()calloc()分配內(nèi)存,調(diào)用free()釋放
作用域分為文件作用域、塊作用域、函數(shù)作用域。
鏈接分為外部鏈接、內(nèi)部鏈接、無鏈接。*/
}
//void func(int a1[const], int a2[restrict], double ar[static 20]) //C99規(guī)定,int* const a1等價int a1[const],int* restrict a2等價int a2[restrict],double ar[static 20]告訴編譯器該數(shù)組至少有20個元素