C++中的引用与C语言的指针的指针运用
引言
其实早就想写这个笔记了,尤其是看到越来越多同学上课不怎么听讲课后多次犯了这个错误后。心里有点捉急,于是想动笔了。网上关于C++引用类型的说明其实有很多,也很通熟易懂,这里推荐一篇文章。那么在这里,我将结合自己的知识储备进行更有针对性的讲解,希望同学们能够耐下心来看完。叙述中或许有些不太严谨或是没用更加精确的术语的地方亦或是有些许错误,希望读者谅解并提出。
我的讲解顺序是:
变量的作用域->C语言的传参规则及指针传参的错误认识->C++的引用类型->两种解决C语言没有引用类型的方法
变量的作用域
关于这一点我简单讲,基本规则就是变量作用域就是所在的花括号所包含的范围。如:
for(int i=0;i<MAX;i++){
......
}
则i的作用域就是这个for循环。所以:
#include <stdio.h>
int main(void){
for(int i=0;i<MAX;i++){
......
}
for(i=0;i<MAX;i++){
......
}
return 0;
}
编译都会不通过,因为对于第二个for循环,i是未定义的。你可以这样简单理解:某个变量只在声明时生成,对所在花括号外面的语句块没有任何作用效果。等到执行完到花括号末尾时,变量就销毁了。函数变量也是如此,这里的函数里的变量指的是如下例
int Add(int a,int b){
int c;
c=a+b;
return c;
}
中的a、b、c。当程序执行到调用Add函数时,变量a、b、c生成并相应的占用一块内存。当函数执行完时,变量a、b、c就被销毁了。
C语言的传参规则及指针传参的错误认识
上个学期学习指针时,老师肯定大都举过类似这样的一个例子:
#include <stdio.h>
void Plus(int a){
a++;
}
void PPlus(int *a){
(*a)++;
}
int main(void){
int b=1;
Plus(b);
printf("%d\n",b);
PPlus(&b);
printf("%d\n",b);
return 0;
}
运行结果为:
1
2
不知道你们当时是怎样想的,我只记得很惊讶,一直在问自己问什么呀???为什么结果不是
2
3
经过老师的讲解,我大概知道了。
因为Plus函数传参是传值调用,函数里面的a变量和主函数里面的b变量是独立,占有不同的内存,只是变量所储存的值相等都是1。于是乎你函数里面的操作只是对函数变量a的操作,对主函数里面的b变量没用任何影响。于是乎就可以理解为函数变量a就是主函数变量b的复制品,对复制品的操作对原变量没有任何影响。
但是指针传参好像就不一样了,因为它是传址调用。通过函数指针变量a可以根据地址进行改变主函数变量b的值。
于是乎,指针的传址调用便深入大脑。于是同学们就记住指针调用不用加取址符这个原则。
确实,在上个学期应对大部分问题时这句话几乎屡试不爽。但是一到这个学期学了数据结构时(特指教材用C++但是老师要求作业要使用C),漏洞百出。那么问题出在哪里呢?
推荐两本书《C与指针》和《C陷阱与缺陷》,廖勇老师推荐的,据说是属于C语言圣经级别的那种。扯远了······《C与指针》书中指出:C语言的传参规则很简单,就是传值调用。
数组参数的传址调用看起来似乎和传值调用相悖。但是,此处其实并无矛盾之处——数组名的值实际上是一个指针,传递给函数的就是这个指针的一份拷贝。
什么意思呢?我们用代码并结合图画来说明:
#include <stdio.h>
#include <string.h>
#define M 5
void Func(int *p){
p[3]++;
return;
}
int main(void){
int a[M];
memset(a,0,sizeof(int)*M);
Func(a);
for(int i=0;i<M;i++){
printf("%d ",a[i]);
}
return 0;
}
如前文我所讲,传值调用就是说形参(这里例子中指p)是实参(这里例子指a)的复制品,而指针作为参数其实并不违背传值调用的原则。p在调用函数时生成,在函数结束时销毁。但是却确确实实对主函数里的数组a的所包含的元素的值进行了改变。
其实,我比较喜欢这样理解。比如第一个例子:
#include <stdio.h>
void Plus(int a){
a++;
}
void PPlus(int *a){
(*a)++;
}
int main(void){
int b=1;
Plus(b);
printf("%d\n",b);
PPlus(&b);
printf("%d\n",b);
return 0;
}
调用Plus函数时首先执行了
int a=b;
语句。而调用PPlus函数时首先执行了
int *a=&b;
语句。
例子
#include <stdio.h>
#include <string.h>
#define M 5
void Func(int *p){
p[3]++;
return;
}
int main(void){
int a[M];
memset(a,0,sizeof(int)*M);
Func(a);
for(int i=0;i<M;i++){
printf("%d ",a[i]);
}
return 0;
}
当调用Func函数时首先执行了
int *p=a;
语句。
理解了以上部分,你就很容易理解这个学期学数据结构时你一直不理解的问题了。比如:
......
void List_Init(ListPtr p) {
p = (ListPtr)malloc(sizeof(ListNode));
if (p == NULL) {
exit(0);
};
p->next = NULL;
return;
}
......
int main(void){
ListPtr head;
List_Init(head);
//printf("OK\n");
List_Insert(head,......);
//printf("OK\n");
......
return 0;
}
你调试时发现程序在打印出第一个OK后就停止工作,于是就认为时List_Insert函数有问题,于是绞尽脑汁结果还是找不出问题来,然后你就很烦,然后你就不想做了,然后你就希望去抄一下别人代码交上去得了······其实你的错误出在初始化函数······
如此真相大白。
C++的引用类型
前面提到的文章讲的就挺好的(戳这里)。
因为C++不是C,所以传参规则就不是仅限于传值调用。我们结合具体实例讲解:
#include <iostream>
using namespace std;
void PPPlus(int &a){
a++;
return;
}
int main(void){
int b=1;
PPPlus(b);
cout<<b<<endl;
return 0;
}
运行结果:
2
其中调用PPPlus函数时发生如图所示情况:
如果还没理解引用,再看第二个例子:
#include <iostream>
using namespace std;
void PPPPlus(int *&a){
(*a)++;
return;
}
int main(void){
int b=1;
int *c=&b;
PPPPlus(c);
cout<<b<<endl;
return 0;
}
运行结果:
2
其中调用PPPPlus函数时发生如图所示情况:
综合两个例子,我们可以清晰地看到,所谓引用就是不像C那样进行复制,引用变量就是原变量地一个别名,对引用变量的操作就是对原变量的操作。
于是乎,我们书上就用引用变量这个方法实现head的初始化:
......
void List_Init(ListPtr &p) {
p = (ListPtr)malloc(sizeof(ListNode));
if (p == NULL) {
exit(0);
};
p->next = NULL;
return;
}
......
int main(void){
ListPtr head;
List_Init(head);
//printf("OK\n");
List_Insert(head,......);
//printf("OK\n");
......
return 0;
}
两种解决C语言没有引用类型的方法
但是不幸的是,老师要求我们用C来实现所有题目。那么有什么办法呢?下面直接给出代码:
代码1
......
ListPtr List_Init() {
ListPtr p ;
p = (ListPtr)malloc(sizeof(ListNode));
if (p == NULL) {
exit(0);
};
p->next = NULL;
return p;
}
......
int main(void){
ListPtr head;
head=List_Init();
//printf("OK\n");
List_Insert(head,......);
//printf("OK\n");
......
return 0;
}
代码2
这个方法就有点技巧了,不过我比较喜欢用这种方法,看似比较复杂但是好处还是挺多的。
void List_Init(ListPtr **p) {
(*p) = (ListPtr)malloc(sizeof(ListNode));
if ((*p) == NULL) {
exit(0);
};
(*p)->next = NULL;
return;
}
......
int main(void){
ListPtr head;
List_Init(&head);
//printf("OK\n");
List_Insert(head,......);
//printf("OK\n");
......
return 0;
}
之所以喜欢用这个方法,是因为这样就保留了函数的返回值类型,我们可以令返回值为你想要的类型,比如bool或者status,以便后续调试。
其实指针的指针运用可以完美去除带头节点的数据结构。你们难道不觉得带头结点真的看起来很不舒服吗?反正我就是有强迫症,要我写带头节点的数据结构我真的写不下去。当然并不是说头结点没一点好处,至少阅读起来比较易懂。
后记
说句老实话,我觉得现在我们学校至少我们学院的同学们充满了一种戾气。不怎么好描述,就是透露出一种很丧的心态。大学四年,“革命”尚未结束,同志们仍需努力啊!
又到了推送小姐姐的时候了,送上新晋女神李惠利和一部剧《请回答1988》。