《编码规范和测试方法——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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章