基数排序完成后的总结:C的值传递特性与typedef、结构体

在写完基数排序后,对于一些函数的写法和语言的理解更加深入了,有一些感想想写下了。

在基数排序中,我写下的整个过程的代码如下,我添加了一些注释来说明每个函数的目的,理清楚过程后写起来其实并不难。

void LSDRadixSort(int a[], int N) {
	PtrToNode List;
	List = ArrToList(a, N);//ArrToList函数:将数组转换成一个链表
	int D,Di;
	Bucket B;
	/*排序开始,D要一直循环,直到完成所有关键字为止*/
	for (D = 1; D <= MaxDigit; D++) {
	
		/*按照D,将链表中的数插入到桶中*/
		ListToBucket(List,B,D);
		
		/*将桶插入链表;为了方便链表插入,这里从最后一个桶开始往链表插入*/
		List = NULL;
		for (Di = Radix-1; Di>=0; Di--) {
			BucketToList(List,B,Di);
		}
		
	}
	ListToArr(List, a, N);//ListToArr函数:将链表转换为数组
}

但是在具体书写每一个函数的时候,遇到了一些小问题,也值得我思考。

1. C语言的值传递特性:

C语言中的函数是通过值传递的,即使是指针也是如此。
在书写ListToBucket(List,B,D)这个函数的时候,我是这么写的。
那么这里就带来了第一个问题:为什么PtrToNode& List这个东西要使用引用而不使用值传递?
毕竟PtrToNode原本就是一个指针了。

/*按照D,将链表中的数插入到桶中*/
   	ListToBucket(List,B,D);
void ListToBucket(PtrToNode& List, Bucket B, int D) {
	/*根据D,将List中的值插入到Bucket B中*/
	PtrToNode& Ptr2ListNode=List;
	int d;
	while (Ptr2ListNode != NULL) {
		/*将这个节点插入到对于的桶中,并且Ptr2ListNode指针要进行更新*/
		d = GetDigit(Ptr2ListNode->key, D);
		/*将这个节点插入到对于的桶中,并且Ptr2ListNode指针要进行更新*/
		InsertNodeToBucket(Ptr2ListNode, B, d);
	}
}

void InsertNodeToBucket(PtrToNode& List,Bucket B,int D) {
	/*将节点List插入到Bucket B的第D个桶*/
	PtrToNode tmp;
		tmp=List->next;
		List->next = B[D].head;
		B[D].head = List;
		List = tmp;
}

于是我们的问题就变成了,如果函数声明为如下,那么能否更新指针呢?从而使得外部的循环
while (Ptr2ListNode != NULL) {

}是有意义的?

void ListToBucket(PtrToNode List, Bucket B, int D);
void InsertNodeToBucket(PtrToNode List,Bucket B,int D);

答案是不行!

因为在参数传递的过程中,指针进行的是值的传递,虽然传递的是指针,但是外部的指针的值不会跟着改变。也就是pt_f可以在函数中随便指,也能改变pt_f指向的对应的节点中的Key的值和Next的值;但是在函数的外部,pt1是不会变化的,也就是说对应的while循环无法正常的迭代。
简单点来说,传入的pt1是按值传递的,它对应的地址的值(key,next)可以被修改,但是pt1本身不会被修改,和我们的目的不同。
在这里插入图片描述

对应的解决方法:

1.考虑使用引用,因为引用在使用的时候也会改变相应的值,而且这样写起来的话更加方便理解一些。也可以考虑使用指针传递,这样的话函数就会变成

void ListToBucket(PtrToNode* Ptr2List, Bucket B, int D);
void InsertNodeToBucket(PtrToNode* Ptr2List,Bucket B,int D);

使用指针的话,在外部调用的时候,我们创建了链表后,就要这么调用函数

PtrToNode* Ptr2List
*Ptr2List=ArrToList(a,N);
ListToBucket(Ptr2List,B,D);

对应的在函数内部,应该需要这么写(以插入节点为例子)

void InsertNodeToBucket(PtrToNode* Ptr2List,Bucket B,int D) {
	/*将节点List插入到Bucket B的第D个桶*/
	PtrToNode tmp,List;//这里相当于把原来传递的List值当成一个新增的变量
	List=*Ptr2List;
		tmp=List->next;
		List->next = B[D].head;
		B[D].head = List;
		*Ptr2List = tmp;
}

总结1:

简单来说,在写函数之前,除了考虑需要哪些变量,要实现哪些功能之外,还需要考虑输入的变量,哪些是需要改变的,需要改变的变量,要么在函数外部已经声明好一个指针,然后在函数使用的时候通过指针传递;要么可以考虑使用引用在函数中如果出现指针也要改变的情况,要尤其注意这个问题!

关于结构体数组,typedef以及函数处理结构体

正确的写法

/*定义一个新结构*/
struct NewStr {
	int x;
	int y;
};
typedef NewStr NS_10[10];//以新结构定义一个数组
//void test(NS_10 b)//正确写法1
//void test(NewStr* b)//正确写法2
//void test(NS_10 b[])//错误写法
void test(NS_10 b) {
	for (int i = 0; i < 10; i++) {
		b[i].x = 2 * i;
	}
}
int main() {
	NS_10 b;
	/*初始化赋值*/
	for (int i = 0; i < 10; i++) {
		b[i].x = i;
	}
	test(b);//函数处理数组
	for (int i = 0; i < 10; i++) {
		cout << b[i].x << endl;
	}
	return 0;
}

正确的写法
test函数这样写也可以

void test(NewStr* b) {  }//最终输出和上面相同

错误的写法

void test(NS_10 b[]) {//误以为要改变b[i]中的值,那么就需要传递b的地址,考虑到int a[]==int* a,于是这里就这么写了
	for (int i = 0; i < 10; i++) {
		b[i]->x = 2 * i;
	}
}
int main() {
	NS_10 b;
	for (int i = 0; i < 10; i++) {
		b[i].x = i;
	}
	test(&b);//考虑到这里要改变b[i]中的x的值,感觉这里这么写似乎也没有什么错
	for (int i = 0; i < 10; i++) {
		cout << b[i].x << endl;
	}
	return 0;
}

在这里插入图片描述

反思:

不能使用void test(NS_10 b[]) ;有下面几个原因:
1.b[i]的基本单位是NS_10,取b[i]的每个跨距为NS_10,即10个int的距离,这是不符合逻辑的,因为我们想取的是b里面的第i个元素,而不是以b为起点的第10*i个元素。
2.为什么这里似乎只是值传递也达到了效果?这里不需要指针传递吗?

答:这里的NS_10 b[] 相当于 NS_10 * Ptrb,相当于b是一个NS_10类型的指针变量,那么我们传进去的参数是&b,在取了b[i]后是一个NS_10的变量,而这个变量也是一个Newstr指针,这个时候取值需要用"->x"。

但是这种写法希望解决的是这个Ptrb它可的值发生变换,也就是说它可能指到另一个NS_10的时候,那么用这个是可以考虑的。或者换句话说,当你有很多个NS_10变量,比如NS_10 a,b,c的时候,你这么用是合理的。但是现在只有一个NS_10 变量b,这么用就不太合理了。

那么最后要思考的是,为什么只传进去一个b就可以改变你需要改变的数的值呢?
第一点:
根据C传递的特性,简单来说就是如果要改变一个值,那么需要输入这个值的地址,从而通过地址来改变这个值。如果你想改变值x,而你输入的变量也是这个x,由于函数值传递是无法改变外部的值的。
第二点
那么需要传进去一个地址,那么可以传b,也可以传&b,传哪个合适,这就要考虑到进位的大小和取值,这个和二维数组a[][]有点像。

typedef Newstr NS_10[10];
NS_10 b;

所以这里的b的变量虽然是NS_10 b,但同时也是 Newstr*,它也是一个指针类型

思考两个问题:

1.如何改变一个结构体/数组中的某个值?

fun(Newstr* a);//a+1时,sizeof(Newstr)++
fun(int a[]);//a+1时,sizeof(int)++
fun(int* a);//a+1时,sizeof(int)++
fun(NS_10 b[]);//sizeof(NS_10)++
fun(NS_10 b);//同Newstr*a,sizeof(Newstr)++
fun(PtrToSrtuct b);//同NS_10 b

简单来说,在函数声明为fun(ClassX A[]);的时候,传进的参数是一个ClassX类的指针,或者说传进的是ClassX类的一个数组A[](array of classX),或者说是多个classX类的对象(multi objects of classX)

2.那么如何改变结构体数组中的值呢?

这里我做了一个图,用来表示声明的时候不同的方法,会导致B[i]向内取元素和向外取元素的区别。

void func(NS_10 B);//B[i]取的是B里面的第i个结构体,向内取
void func(NS_10 B[]);//B[i]取的是B之后的第i个结构体数组,向外取

在这里插入图片描述

最后补充一个知乎上关于typedef的回答

作者:婉儿飞飞
链接:https://www.zhihu.com/question/29798061/answer/144423125
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值