《編碼規範和測試方法——C/C++版》作業 ·003——宏定義作用整理、設計刪除數組元素的函數

一、宏定義(#define)的作用

#define總體來說,作用就是將一個標識符定義爲一個表達式,然後在預編譯的階段直接將程序進行替換工作——將代碼中所有宏定義的標識符替換爲對應的表達式。

1、普通宏

用法如下:

#define 宏名 表達式

主要是用於定義一些常量。比如下面這段代碼:

#define N 1005
int main()
{
	int a[N];
	for (int i = 0; i < N; ++i)
		a[i] = N - i;
	
	return 0;
}

在預編譯階段就會被替換成:

int main()
{
	int a[1005];
	for (int i = 0; i < 1005; ++i)
		a[i] = 1005 - i;
	return 0;
}

但是宏定義往往需要勤加括號,不然直接替換後往往因爲表達式中運算符優先級、結合律的問題出錯,比如下面這段代碼:

#include <iostream>
#define ADDTEST 1 + 2

using namespace std;

int main()
{
	// 期望結果:1
	// 實際結果:5
	cout << ADDTEST / ADDTEST << endl;
	return 0;
}

正是以爲沒有勤加括號,ADDTEST / ADDTEST在預編譯時被替換成了1 + 2 / 1 + 2,再根據+/這兩個運算符的優先級和結合律,表達的意義自然就變了。

另外,宏定義的量的數據類型也沒有顯式地表達出來,有時候可能會涉及數據類型的問題。

普通的宏定義在C裏面用的還算多,但是由於上面的缺陷,到了C++基本就被const常量給取代了。

2、帶參宏

用法如下:

#define 宏名(參數) 表達式

主要是用於定義一些簡單的函數。比如下面這段代碼:

#include <iostream>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
int main()
{
	cout << MAX(1, 6) << endl;
	return 0;
}

在預編譯階段就會被替換成:

#include <iostream>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
using namespace std;
int main()
{
	cout << ((1) > (6) ? (1) : (6)) << endl;
	return 0;
}

類似地,帶參宏如果不勤加括號,也會存在運算符優先級、結合律的問題容易出錯。

另外,帶參宏也不會去判斷參數的數據類型,因此往往也會有數據類型的問題

C中往往用帶參宏來定義一些簡單表達式來代替一些簡單的函數,從而提高程序的效率、減少開銷,而到了C++裏基本就被內聯函數(inline)給替代了。

因爲內聯函數的功能就是在調用時把內聯函數的代碼段放到調用的地方的代碼段中,而且還能檢查參數的數據類型。不過同樣地,非常複雜的函數不宜寫成內聯函數。

【注】:特殊用法

帶參宏中有一些特殊的使用方法:

特殊用法 作用
# #x將參數x擴展爲字符串"x"
## a##b將參數b連接到參數a後面
#@ #@x將參數x擴展爲字符'x'

3、其他用法和注意點

  • 帶參宏的特殊用法中,如果參數爲宏定義的量,在帶參宏中不會被再次展開(替換)
    #include <iostream>
    
    #define N 1005
    #define TEST(x) x##3##x
    
    using namespace std;
    
    int main()
    {
    	cout << TEST(N) << endl;  // 等價於下面的代碼
    	cout << N3N << endl;      // error: “N3N”: 未聲明的標識符
    	return 0;
    }
    
  • 可以使用續行符\定義一個多行宏
  • 可以使用#undef來消去一個宏
  • C中宏定義#define一個常量的方式在C++中可以用const來替換
  • C中宏定義#define一組常量的方式在C++中可以用enum來替換
  • C中宏定義#define一個表達式的方式在C++中可以用內聯函數inline來替換

二、實際問題(函數設計)

1、原題

編寫一個deleteValue函數,實現“對給定的數組,刪除指定的元素”的功能。
編寫一個display函數用於測試,顯示數組實際有效的元素。
要求:符合編碼規範、邏輯正確。

2、題解參考

本來是想直接操作數組,不用動態申請空間的,但是仔細想了下,如果刪一個元素就把後面的元素往前挪,這樣的算法時間複雜度O(n2)O(n^2),數組太大效率就會很慢。那就用空間換時間,在查找的時候把符合條件的元素複製到臨時的數組裏,最後再複製回去,這樣時間複雜度就是O(n)O(n).

另外,動態申請空間後釋放的局部指針沒有置空(NULL)的原因是下一步已經是函數結束,作用域都生命期也該結束了,不想再多此一舉了。

另外,其實我還是比較傾向於tmp[cnt++] = arr[i]這種寫法,不過既然編碼規範認爲這樣寫不好就算了吧。

雖然類和對象設計的編碼規範還沒有講到,但是直覺上感覺會有一條“給所有成員函數中的數據成員加上this->”的規範。

(1).面向過程的設計

#include <iostream>

using namespace std;

int deleteValue(int arr[], const int len, const int num);
void display(const int arr[], const int len);

int main()
{
	int test[] = { -5, 5, 1, 3, 2, 3, 0, 0, 1, 2, 5, 5 };
	int len = sizeof(test) / sizeof(int);  // 數組長度
	int num;

	cout << "原數組如下:" << endl;
	display(test, len);

	cout << "請輸入要刪除的元素(輸入Ctrl+Z結束輸入): ";
	while (cin >> num)
	{
		len -= deleteValue(test, len, num);
		cout << "刪除該元素後數組如下: " << endl;
		display(test, len);
		cout << "請輸入要刪除的元素(輸入Ctrl+Z結束輸入): ";
	}

	return 0;
}


int deleteValue(int arr[], const int len, const int num)
{
	int cnt = 0;                    // 計數器,用來統計剩餘的元素個數
	int nSize = len > 0 ? len : 1;  // 計算實際需要申請的空間,避免發生類似new int[0]、new int[-1]這些情形
	int* tmp = new int[nSize];

	if (NULL == tmp)                // 判斷是否成功申請空間
	{
		cout << "申請空間失敗" << endl;
		return -1;
	}

	for (int i = 0; i < len; ++i)   // 將數組刪除元素num後的結果存入數組tmp中
	{
		if (arr[i] != num)
		{
			tmp[cnt] = arr[i];
			++cnt;
		}
	}

	for (int i = 0; i < cnt; ++i)   // 將數組tmp的內容拷貝到原數組中
	{
		arr[i] = tmp[i];
	}

	delete[] tmp;                   // 釋放動態申請的空間(按照編碼規範,釋放完是應該置空指針tmp的,不過考慮到下面直接return了,就算了吧)
	return len - cnt;               // 返回成功刪除的元素個數
}

void display(const int arr[], const int len)
{
	if (len == 0)                   // 空數組打印提示
	{
		cout << "【數組已空】\n" << endl;
		return;
	}

	for (int i = 0; i < len; ++i)   // 非空數組循環打印
	{
		cout << arr[i] << ' ';
	}
	cout << "\n" << endl;
}

(2).面向對象的設計

#include <iostream>

using namespace std;

class Array
{
public:
	Array();
	Array(const int datas[], const int size);
	~Array();
	void deleteValue(const int num);
	void display()const;
private:
	int* arr;
	int len;
};

Array::Array()
{
	arr = NULL;
	len = 0;
}

Array::Array(const int datas[], const int size)
	:len(size)
{
	int nSize = size > 0 ? size : 1;

	this->arr = new int[nSize];
	if (NULL == arr)
	{
		cout << "申請空間失敗" << endl;
		return;
	}
	
	for (int i = 0; i < size; ++i)
	{
		this->arr[i] = datas[i];
	}
}

Array::~Array()
{
	if (NULL != this->arr)
		delete[] this->arr;
}

void Array::deleteValue(const int num)
{
	int cnt = 0;
	int nSize = this->len > 0 ? this->len : 1;
	int* tmp = new int[nSize];

	if (NULL == tmp)
	{
		cout << "申請空間失敗" << endl;
		return;
	}

	for (int i = 0; i < this->len; ++i)
	{
		if (this->arr[i] != num)
		{
			tmp[cnt] = this->arr[i];
			++cnt;
		}
	}
	
	if (NULL != this->arr)  // 釋放原數組原來的內存(如果非空)
	{
		delete[] this->arr;
	}
	this->arr = tmp;        // 轉換指向(視爲複製)
	this->len = cnt;        // 更新刪除後的元素個數
}

void Array::display()const
{
	if (this->len == 0)                   // 空數組打印提示
	{
		cout << "【數組已空】\n" << endl;
		return;
	}

	for (int i = 0; i < this->len; ++i)   // 非空數組循環打印
	{
		cout << this->arr[i] << ' ';
	}
	cout << "\n" << endl;
}

int main()
{
	int test[] = { -5, 5, 1, 3, 2, 3, 0, 0, 1, 2, 5, 5 };
	int len = sizeof(test) / sizeof(int);  // 數組長度
	int num;

	Array arr_test = Array(test, len);

	cout << "原數組如下:" << endl;
	arr_test.display();

	cout << "請輸入要刪除的元素(輸入Ctrl+Z結束輸入): ";
	while (cin >> num)
	{
		arr_test.deleteValue(num);
		cout << "刪除該元素後數組如下: " << endl;
		arr_test.display();
		cout << "請輸入要刪除的元素(輸入Ctrl+Z結束輸入): ";
	}

	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章