1. 程式人生 > >C語言重要知識點回顧

C語言重要知識點回顧

太久沒有看C語言相關知識了,開始要整理回顧一些重點知識點啦,因為各大公司筆試還是有許多C語言相關的題,做個複習。

const 關鍵字與指標修飾使用

普通指標使用:

//普通指標使用,我們通過 i 或者 p 指標都能改變變數值
void test1()
{
	int i = 1;
	int * p = &i;

	printf("p=%d\n",*p);
	i = 2;
	printf("p=%d\n",*p);
	(*p)++;
	printf("p=%d\n",*p);
	printf("i=%d\n",i);
}

輸出結果:


這個結果是我們好理解的。

接著 const int *p 問題

// const int *p 表示p 所指的物件是隻讀不可以改變的,但p 指標可以指向其他地址
void test2()
{
	int i = 1;
	int j = 100;
	const int * p = &i;

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

	i = 2;

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

	p = &j;
	printf("p=%d\n",*p);

	/*
	(*p)++;// 出錯 error:increment of read-only lacation '*p'
	printf("p=%d\n",*p);
	*/
}
輸出結果:

這裡我們發現指標p 我們可以隨便調整指向哪塊已知的記憶體空間,但是不能通過 給*p 複製來改變指標所指的物件。

int const *p 和上面const int *p  效果一樣這裡就不多說啦。

接下來說 int * const p 形式,如下測試程式碼:

// int * const p 表示指標p 不可修改,但是指標p 所指向的內容可以修改
void test3()
{
	int i = 1;
	int j = 100;
	int * const p = &i;

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

	i = 2;

	printf("p=%d\n",*p);
	/*
	p = &j;// error:assignment of read-only variable 'p'
	printf("p=%d\n",*p);
	*/
	(*p)++;
	printf("p=%d\n",*p);
	printf("i=%d\n",i);
}

輸出結果:

最後一種情況就是上面情況結合在一起const int * const p 這樣就是p 指標無法修改,p 指標所指的內容也無法修改。

C與指標第六章習題

1.

char * find_char(char const * source , char const *chars)
{
	if(source==NULL || chars==NULL)
		return NULL;
	char const * cp;
	for(;*source!='\0';source++)
	{
		// 這裡每次遍歷chars 中內容
		for(cp=chars;*cp!='\0';cp++)
		{
			if(*source == *cp)
				return (char *)source;
		}
	}
	return NULL;
}
實現中發現一個問題:char a[] 與 char *a 的區別

char a[]在執行時賦值,值會從靜態區賦值到函式的棧中,對它進行修改不會產生任何問題。char *a在編譯時就確定了,a指向靜態區中的值,沒有賦值到函式棧中, 因此對指標的內容進行修改會產生錯誤。

char * match(char * str,char const *substr)
{
	while(*substr != '\0')
	{
		if(*str++ != *substr++)
			return NULL;
	}
	return str;
}
int Del_substr(char *str,char const *substr)
{
	char * next;
	char * orig = str;
	while(*str != '\0'){
		next = match(str,substr);
		if(next != NULL)
			break;
		str++;
	}
	if(*str == NULL)
		return 0;
	printf("outside\n");
	while((*str) != '\0')
	{
		*str = *next;
		str++;
		next++;

	}
	printf("%s\n",orig);
	return 1;
}

3.
void reverse_string(char *str)
{
	if(str == NULL)
		return;
    char *p = str;
    int count = 0;
    char ch;
    for(;*p!='\0';p++)
	{
		count++;
	}
	p = str;
	char * end = p + count -1;
	while(p < end)
	{
		ch = *p;
		*p = *end;
		*end = ch;
		p++;
		end--;
	}
	*(str+count) = '\0';
	printf("%s\n",str);
}

指向陣列的指標VS指標陣列

指向陣列的指標:

int vector[10], *vp = vector; 這個宣告是合法的,它為整型陣列分配記憶體,並把vp 宣告為指向整型的的指標。

int matrix[2][3] matrix 並不是指向整型的的指標,而是一個指向整型陣列的指標,我們應該如何宣告指向陣列的指標?

int (*mp)[3]這裡要帶上第二維的資料控制,不是mp指標自增操作不確定能跳過多少長度。

int matrix[2][3] = {{1,2,3},{4,5,6}};
    int *p = &matrix[0][0];
    printf("%d\n",*p);
    printf("%d\n",*++p);
    printf("%d\n",*++p);

如上程式碼,指標p 指向陣列中第一個元素,然後指標自增1 ,指向了第二個數字,所以上面輸出就是:1,2,3 ,我們一直要確定好一件事情就是指標型別,因為型別決定了指標自增1是能跳動多大的距離。
int matrix[2][3] = {{1,2,3},{4,5,6}};
    int (*mp)[3];
	mp = matrix;
    printf("%d\n",(*mp)[0]);
    printf("%d\n",(*mp)[1]);
    printf("%d\n",(*++mp)[0]);

如上程式碼:定義mp 為指向擁有3個整型元素的陣列的指標,當對mp 與整數相加時,該整數值根據3這個長度調整,所以mp++ 導致指標mp 指向陣列下一行陣列元素。

所以上述程式碼輸出:1,2,4 這裡就可以告訴我們如何去對二維陣列元素通過指標進行操作。

指標陣列:

正如你可以建立整型陣列一樣,你也可以宣告指標陣列,如下面:

int *api[10] ,api 有十個元素,每個元素是指向int 型的指標。

再看個複雜點結構:

char const *keyword[] = {
		"do",
		"for",
		"if",
		"register",
		"return",
		"switch",
		"while"
    };

keyword 是一個指標陣列,陣列中每個元素都指向一個char型陣列。當我們需要查詢某個關鍵字時可以遍歷該指標陣列,如下:
int lookup_keyword(char const * const desired_word,char const *keyword_table[],int const size)
{
	char const **kwp;
	/*
	char (*p)[10];// 這裡搞清楚型別啊,keyword_table 是char **
	*/
	// 查詢kewword_table中每個單詞
	for(kwp = keyword_table;kwp < keyword_table + size;kwp++){

		printf("%s\n",*kwp);
		*kwp = "hello";//陣列中內容可以改變,所以*kwp 可以指向別的內容
		if(strcmp(desired_word,*kwp) == 0){
			return kwp - keyword_table;
		}
	}
	return -1;
}

這裡需要注意為什麼kwp 定義為指標的指標? 分析一下,keyword_table 是陣列起始位置是指標,而陣列中元素也是指標,所以當要引用陣列中元素時必須定義為指標的指標來遍歷該陣列。

如果上述結構定義為二維陣列這樣:

   char const keywordMatrix[][9]={
		"do",
		"for",
		"if",
		"register",
		"return",
		"switch",
		"while"
    };

實現上述查詢相同功能則需要進行改動:
int lookup_keywordMatrix(char const * const desired_word,char const (*keyword_table)[9],int const size)
{
	char const (*kwp)[9];
	for(kwp=keyword_table;kwp<keyword_table+size;kwp++)
	{
		if(strcmp((char *)kwp,desired_word) == 0)
		{
			return kwp - keyword_table;
		}
	}
	return -1;
}

首先傳參就需要改變,這裡定義的 char const(*keyword_table)[9] 是指向char型陣列的指標,定義kwp同樣需要這樣為:char const (*kwp)[9] ,

所以在使用strcmp 函式時需要型別強制轉換。

小結:

陣列名是指向陣列第一個元素的指標。這裡有兩個例外,sizeof返回整個陣列佔用的位元組而不是一個指標所佔用的位元組。

int a[] 對 &a 操作返回是指向整個陣列的指標。

指標和陣列不相等。當我們宣告一個數組時,同時就分配了記憶體空間,但是宣告一個指標時,只是分配了容納指標本身空間。

當陣列名作為函式引數傳遞的,實際傳遞給函式是指向陣列第一個元素的指標。 函式所接收的引數實際為原引數的拷貝,所以函式可以對其進行操縱不影響實際引數,但是執行期間修改陣列元素會影響原先陣列元素。

結構體與記憶體分配:

結構體最基本的兩種訪問方式,關於記憶體分配,C語言中使用是malloc 和 free 。

malloc函式從記憶體池中提取一塊合適的記憶體,並向呼叫程式返回一個指向這塊記憶體的指標。你需要自己手動對這塊記憶體進行初始化,malloc函式分配是一塊連續的記憶體,

使用malloc函式時一定要注意malloc分配記憶體空間是否成功,如果不成功malloc函式會返回NULL,所以好的程式設計習慣一定是檢查分配記憶體空間。

此外malloc函式返回是void * 指標,因為這個返回型別問題,我們使用malloc經常會需要強制型別轉換。

動態記憶體常見錯誤:

NULL指標解引用操作、分配記憶體操作越界、釋放並非動態分配記憶體、釋放一塊動態分配記憶體的一部分、動態記憶體釋放後繼續使用等。

通過實際對單向連結串列操作來熟悉結構體和記憶體分配。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

typedef struct LinkList{
	int value;
	LinkList * next;
}*ListPoint;

void insert_Node(LinkList **head,int value)
{
	ListPoint pre,current;
	pre = NULL;
	current = *head;
	while(current && current->value < value)
	{
		pre = current;
		current = current->next;
	}
	LinkList * new_node = (LinkList *)malloc(sizeof(LinkList));
	//好的程式設計習慣需要每次分配記憶體檢查
	if(new_node == NULL)
	{
		printf("malloc memory error !!!");
		return;
	}
	new_node->value = value;
	new_node->next = current;
	// 意味著插入連結串列起始位置
	if(pre == NULL)
	{
		printf("test here\n");
		*head = new_node;

	}
	else{
		pre->next = new_node;
	}
}

void Print_LinkList(LinkList *head)
{
	if(head == NULL)
	{
		printf("empty LinkList\n");
		return;
	}
	while(head !=NULL)
	{
		printf("%d",head->value);
		head = head->next;
	}
}

int main()
{

	int arr[6] = {3,2,1,6,4,5};
	ListPoint head = NULL;
	for(int i=0;i<6;i++)
	{
		insert_Node(&head,arr[i]);
	}
	Print_LinkList(head);

	for(int i=0;i<6;i++)
	{
		printf("haha\n");
	}
	return 0;
}