c语言中变量的引用传递和指针

时间:2021-05-04 08:54:13
掌握引用和指针的区别
引用型变量存储的是变量的地址,指针存储的也是变量的地址,所以本质上来说二者是一样的。
使用引用型变量,子函数中所有的操作会直接修改主函数中的实参。
正常情况下,使用变量的引用总是没有问题的,引用是地址的拷贝。
编程的本质不就是改变量么?通过引用型变量和指针型变量可以让子函数直接改主函数中的变量,而不再需要为形参开辟内存。

//子函数可以主函数前面声明,也可以在主函数中声明,总之一定要在调用前
//下面这个例子说明“不能企图通过改变形参的值来改变实参”
#include "stdafx.h"
#include <stdio.h>
void swap(int x,int y);
void main()
{
    //void swap(int &x,int &y);
    int a=3,b=5;
    swap(a,b);
    printf("a=%d b=%d\n",a,b);
}
void swap(int x,int y)
{
    int temp;
    temp=x;
    x=y;
    y=temp;
}
//运行结果:a b并未发生交换
如果不用指针做形参或者引用型变量做形参(如上所示), 程序在调用子函数时会为x y重新开辟内存,并将实参的值复制到x y中去,然后在swap函数中,
x y确实发生交换了,但这跟主函数中的a b毫无关系呀,a b并未发生改变呀。子函数调用结束后形参所占内存自动释放。
引用型变量和指针其实是将主函数中变量的地址传递给了子函数,这样子函数直接去操作主函数中的变量,并不会再为形参开辟内存。
 

//通过引用型变量,子函数直接改主函数中定义的变量
#include "stdafx.h"
#include <stdio.h>
void swap(int &x,int &y);
void main()
{
    //void swap(int &x,int &y);
    int a=3,b=5;
    swap(a,b);
    printf("a=%d b=%d\n",a,b);
}
void swap(int &x,int &y)
{
    int temp;
    temp=x;
    x=y;
    y=temp;
}
//运行结果:完成交换
//通过引用型变量或者指针调用子函数,子函数会直接改主函数中的变量。
//子函数直接在实参地址上改变量,所以改之前如果需要备份就要备份。

//如果调用的子函数有返回值,就可以不用引用变量,函数调用时会为形参中的x,y开辟内存,将计算的结果返回给主函数后,形参开辟的内存会被释放掉。
#include <stdio.h>
int add(int x,int y);
void main()
{
    int a=3,b=5;
    int sum=add(a,b);
    printf("sum=%d\n",sum);
}
int add(int x,int y)
{
    return x+y;
}

//使用引用型变量,子函数调用会直接对主函数中的变量做加法,不会为形参x y开辟内存,执行效率变高。
#include <stdio.h>
int add(int &x,int &y);
void main()
{
    int a=3,b=5;
    int sum=add(a,b);
    printf("sum=%d\n",sum);
}
int add(int &x,int &y)
{
    return x+y;
}

//关于引用型变量我再写个例子:
#include <stdio.h>
int add(int &x,int &y);
void main()
{
    int a=3,b=5;
    int sum=add(a,b);
    printf("sum=%d\n",sum);
}
int add(int &x,int &y)
{
    x=x+1;
    y=y+1;
    return x+y;
}
/*
运行结果:sum=10
程序执行流程:用了引用变量后,子函数不会再为形参开辟内存,而是直接对a b操作,在原来a的内存中对a加1变为4,在原来b的内存中对b加1变为6,
然后直接相加将10赋给sum,一句话来总结: 用了引用变量后,就不再为形参开辟内存,所有操作都是直接修改实参变量。
调用子函数会将主函数中的变量直接改掉,如果在主函数后面继续使用a b时,一定要注意这两个变量已经被修改了。
*/

//下面写一个调用排序的程序,当用数组名做实参时,相当于使用引用型变量,因为数组名就表示数组首元素的地址,这和引用型变量与指针是一样的
#include <stdio.h>
void sort(int arr[]);//声明
void main()
{
    int a[]={9,8,7,6,5,4,3,2,1,0};//10个数.写成int[] a编译会报错(java中写法)
    sort(a);//数组名代表数组首元素的地址
    for(int i=0;i<10;i++)
        printf("%d ",a[i]);
}
void sort(int arr[])
{
    int min=0;
    for(int i=0;i<9;i++)//10个数做9趟比较
    {
        min=i;//将min定义在循环外面,不然会在循环中重复定义(效率低)
        for(int j=i+1;j<10;j++)//for中的起始条件和结束条件其实定义在循环外面,只有j++是循环体中的内容(深刻理解for和while的关系,能掌握二者的转换)
        {
            if(arr[j]<arr[min])
                min=j;
        }
        int temp=arr[min];
        arr[min]=arr[i];
        arr[i]=temp;
    }
}

/*
下面用指针做参数测试一下,形参和实参都是指针型变量。
事实上这里面指针型变量和数组名能随便替换,还可以选择数组名做实参,指针做形参。
数组名代表数组首元素的地址,并且这个地址可以赋给指针变量p
*/
#include <stdio.h>
void sort(int *arr);//声明
void main()
{
    int a[]={9,8,7,6,5,4,3,2,1,0};//10个数
    int* p=a;//将数组首元素的地址赋给指针变量p,*挨着int写还是p写都是可以的
    sort(p);
    for(int i=0;i<10;i++)
        printf("%d ",a[i]);
}
void sort(int *arr)
{
    int min=0;
    for(int i=0;i<9;i++)//10个数做9趟比较
    {
        min=i;
        for(int j=i+1;j<10;j++)
        {
            if(*(arr+j)<*(arr+min))//if(arr[j]<arr[min])
                min=j;
        }
        int temp=arr[min];
        arr[min]=arr[i];
        arr[i]=temp;
    }
}

//下面这个例子用数组名做实参,指针做形参。事实上(int *arr)和(int arr[])是一样的
#include <stdio.h>
void sort(int *arr);//声明
void main()
{
    int a[]={9,8,7,6,5,4,3,2,1,0};//10个数
    sort(a);//数组名做实参
    for(int i=0;i<10;i++)
        printf("%d ",a[i]);
}
void sort(int *arr)
{
    int min=0;
    for(int i=0;i<9;i++)//10个数做9趟比较
    {
        min=i;
        for(int j=i+1;j<10;j++)
        {
            if(*(arr+j)<*(arr+min))//if(arr[j]<arr[min])
                min=j;
        }
        int temp=arr[min];
        arr[min]=arr[i];
        arr[i]=temp;
    }
}
 
 
我认为编程的两个难题,一个是多返回值,另一个是通过参数调用函数。(第二个问题总是不可避免的,需要参数调用的地方一定是需要的)
解决多返回值的问题,可以使用引用传递,也可以使用下面这种方式。
// 将我们想要通过子函数调用改变的变量首先在主函数中定义好并赋初值,然后将这些变量的引用作为子函数的形参。
// 子函数调用结束后,主函数中的这些变量自然就被改了。
#include "stdafx.h"
#include <stdio.h>
void add(int &x,int &y,int &sum);//引用型变量会使子函数直接改主函数中的变量
void main()
{
    int a=3,b=5;
    int sum=0;
    add(a,b,sum);//执行过这句代码后,sum值自然被改掉
    printf("sum=%d\n",sum);
}
void add(int &x,int &y,int &sum)//将返回值直接定义为子函数的参数,这样返回值就能写为空了
{
    sum=x+y;
}

//不用定义全局变量,通过返回值空的子函数找到数组中的最大元素以及下标
#include "stdafx.h"
#include <stdio.h>
void searchMax(int arr[],int &max,int &index_max);//引用型变量会使子函数直接改主函数中的变量
void main()
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    for(int i=0;i<10;i++){
        printf("%d\t",arr[i]);
    }
    int max=-1;
    int index_max=0;
    searchMax(arr,max,index_max);
    printf("max=%d\n",max);
    printf("index_max=%d\n",index_max);
}
void searchMax(int arr[],int &max,int &index_max)//将返回值直接定义为子函数的参数,这样就不需要返回值
{
    for(int i=0;i<10;i++){
        if(arr[i]>max){
            max=arr[i];
            index_max=i;
        }
    }
}

//这是使用全局变量的写法
#include "stdafx.h"
#include <stdio.h>
int searchMax(int arr[]);
int index_max=0;
void main()
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    for(int i=0;i<10;i++){
        printf("%d\t",arr[i]);
    }
    int max=searchMax(arr);
    printf("max=%d\n",max);
    printf("index_max=%d\n",index_max);
}
int searchMax(int arr[])
{
    int max=-1;
    for(int i=0;i<10;i++){
        if(arr[i]>max){
            max=arr[i];
            index_max=i;
        }
    }
    return max;
}

//将数组所有元素自增1,程序执行成功
//形参中使用(int arr[]),相当于引用型变量
//所有用数组做子函数参数的地方,都是引用传递。
#include "stdafx.h"
#include <stdio.h>
void changeArr(int arr[]);
void main()
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    int i;
    for(i=0;i<10;i++){ //不能将int i=0写在for()里面,这是c语法
        printf("%d\t",arr[i]);
    }
    changeArr(arr);
    for(i=0;i<10;i++){
        printf("%d\t",arr[i]);
    }
}
void changeArr(int arr[])
{
    for(int i=0;i<10;i++){
        arr[i]++;
    }
}

//数组不是c语言中的基本数据类型,所以不能返回数组,只能返回一个指向数组的指针
// 因为数组的内存开辟在堆上,所以子函数结束后并不会被释放,子函数只需要将指针返回给主函数,主函数可随时访问。
#include "stdafx.h"
#include<iostream>
using namespace std;
int* Array()
    {  
     int *a;
     a=new int [10];
     for(int i=0;i<10;i++)
     {
          a[i]=i+1;
     }
     return a;
    }
void main()
    {
     int *b;
     b=Array();

     for(int i=0;i<10;i++)
      cout<<b[i]<<" ";
      cout<<endl;
    }

//所有用数组做子函数参数的地方,都是引用传递。即如果用数组做子函数参数,主函数中的参数一定会被改掉,所以如果需要备份就要提前拷贝好。
#include "stdafx.h"
#include <stdio.h>
void changeArr(int arr[]);
void main()
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    int arr_copy[]={2,23,89,1,90,112,34,13,234,89};
    int i;
    changeArr(arr_copy);
    printf("修改之前的arr:\n");
    for(i=0;i<10;i++){
        printf("%d\t",arr[i]);
    }
    printf("修改之后的arr:\n");
    for(i=0;i<10;i++){
        printf("%d\t",arr_copy[i]);
    }
}
void changeArr(int arr[])
{
    for(int i=0;i<10;i++){
        arr[i]++;
    }
}

//之前做图像处理课作业的时候,想对一张图灰度化,但是原图又要保留下来,就在主函数中申请一个和原图等大的矩阵,全填上0,
然后将原图的指针和空白图的指针(或者数组名)全部作为参数传递给灰度化函数,灰度化函数根据原图像素修改后重写空白图像,执行结束后主函数中的空白图自然被写成灰度图了,
这样也不需要返回值。
//这种方式比在子函数中创建数组,开辟内存,然后返回指针的方式好。当然这种方式也是可以的,因为new开辟的内存在堆上,子函数结束后并不会被释放,所以子函数把指针返回给主函数,主函数可随时访问。
#include "stdafx.h"
#include <stdio.h>
void changeArr(int arr[],int arr_copy[]);
void main()
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    int arr_copy[]={0,0,0,0,0,0,0,0,0,0};
    int i;

    printf("初始化的arr_copy:\n");
    for(i=0;i<10;i++){
        printf("%d\t",arr_copy[i]);
    }
    changeArr(arr,arr_copy);//根据arr去修改arr_copy
    printf("修改之后的arr_copy:\n");
    for(i=0;i<10;i++){
        printf("%d\t",arr_copy[i]);
    }
}
void changeArr(int arr[],int arr_copy[])
{
    for(int i=0;i<10;i++){
        arr_copy[i]=arr[i]+1;
    }
}

//将数组改为指针来做子函数的参数
#include "stdafx.h"
#include <stdio.h>
void changeArr(int* before,int* after);
void main()
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    int arr_copy[]={0,0,0,0,0,0,0,0,0,0};
    int i;
    int* before=arr;
    int* after=arr_copy;

    printf("初始化的arr_copy:\n");
    for(i=0;i<10;i++){
        printf("%d\t",after[i]);
    }
    changeArr(before,after);
    printf("修改之后的arr_copy:\n");
    for(i=0;i<10;i++){
        printf("%d\t",after[i]);
    }
}
void changeArr(int* before,int* after)
{
    for(int i=0;i<10;i++){
        after[i]=before[i]+1;
    }
}

在面向对象的编程中,new会为对象开辟内存,其实就是为对象的所有成员变量开辟内存,成员方法修改成员属性直接就改了,改了之后原来的成员属性值自然
就被覆盖了,我感觉Java这种修改成员变量的方式类似中c中的定义全局变量。用new运算符开辟出的内存在堆上。

单链表的增删改查,其实子函数所用的形参都是单链表的引用型变量&list,增删改查都写成返回值为空的函数,直接修改主函数中的链表。
如果想要将修改之前的链表也保存下来,那就在修改之前(调用子函数之前)做个备份。

关于指针类型:
在32位操作系统中,地址总线是32位的,所以最大寻址空间就是4GB,指针如果想要对所有空间寻址,指针也必须保证为32位,也就是4个字节。
在64位操作系统中,地址总线是64位的,指针如果想要寻址到2的64次方B,指针就必须保证为64位,也就是8个字节。
虽然在程序中定义int* pointer;float* pointer;char* pointer;不论指针指向哪种类型的数据,指针本身所占的字节数都是一样的,都是指向该变量所在
内存的首地址,如果发现指针指向int类型的变量,那就往下取4个字节的数据拿出来,如果发现指针指向float类型的变量,那就往下取8个字节的数据拿出来。

深入理解指针会更好的理解内存分配机制,现在我们来重新审视一下计算机系统,计算机的cpu由运算器,控制器和寄存器组成,控制器中的PC寄存器时刻指向
当前指令在内存的地址,并且pc寄存器每取一条指令就自动增1(地址偏移4B),也就是说控制器根据pc所指地址逐条从内存中取出指令并执行。在遇到函数调
用时pc自然能指向子函数的入口地址,当然在调用之前会将现场情况保存下来,便于函数调用结束后返回主函数。
运算器能完成的基本运算只有加法,移位,和逻辑与或非,乘法是通过加法和移位来完成的。

c语言编程中,如果不使用全局变量的话,可以将主函数中想要被修改的变量的引用作为子函数的参数,通过子函数调用就可以直接修改,不再需要返回值。

面向对象的编程中,将想要通过成员方法修改的变量全部定义为类的成员变量,这样类的成员方法就可以直接使用并且修改成员变量,
成员方法被调用后,成员变量自然发生改变,这种类似c语言中的定义全局变量,不需要返回值。

编程的本质就是修改变量。
调试程序的本质就是跟踪变量。
将不懂的代码单独抽出来进行测试。
不论是面向过程的编程,面向对象的编程,还是函数式编程,其实都是面向变量的编程。
所以解决任何一个编程问题,第一想法都是要定义哪些变量。
主函数中定义变量并初始化,如果通过子函数调用来改变这些变量,要么通过返回值,要么通过引用传递,这正是函数调用的关键所在。
 c语言只能返回基本数据类型,不能返回数组,链表,结构体,但是可以返回他们的指针。
对32位系统而言,地址总线32位,同样指针也是32位,不论指向什么类型变量的指针都是32位,所以指针也算是一种基本数据类型。
 
我建议在需要子函数返回数组,链表,结构体的情况下,并不使用返回指针的写法。
直接将子函数写成空返回值,将需要改变的数组,链表直接在主函数中定义好并赋初值, 然后将其作为参数(引用型变量)传给子函数,
子函数执行过程中直接修改主函数中的数组和链表,这样子函数调用结束后,主函数中定义的数组和链表自然就被改变了。

// delete()函数只能用来释放指针
//子函数中定义的变量在调用结束后并不会被释放,除非被人为的用delete函数释放掉
//新建工程win32 console application
#include "stdafx.h"
#include <stdio.h>
int* sum(int a,int b);
int main(int argc, char* argv[])
{
    int a=10;
    int b=12;
    int* sum1;
    sum1=sum(a,b);
    printf("sum=%d\n",*sum1);
    delete(sum1);
    //printf("sum=%d\n",*sum1);
    return 0;
}
int* sum(int a,int b) 
{
    int sum;
    int* sum_pointer;
    sum_pointer=&sum;
    sum=a+b;
    return sum_pointer;
}

//子函数中定义的数组(new出来的内存在堆上)在调用结束后并不会被释放,只要将数组指针返回给主函数,主函数随时可以访问这个数组
// 在子函数中new出来的内存分配在堆上,除非手动释放,否则不会释放。主函数如果想要访问这块内存,子函数就把这块内存的指针返回给主函数。
// 在子函数中定义的基本类型的变量,分配在栈上,子函数执行结束后自然会被释放,主函数如果想要这个变量,子函数就要把这个变量作为返回值返回给主函数。
#include "stdafx.h"
int* changeArr(int arr[],int arrSize);
//void main(){}
int main(int argc, char* argv[])
{
    int arr[]={2,23,89,1,90,112,34,13,234,89};
    int* arr_copy_pointer;
    arr_copy_pointer=changeArr(arr,10);
    for(int i=0;i<10;i++){
        //printf("%d\t",arr_copy_pointer[i]);
        printf("%d\t",*(arr_copy_pointer+i));
    }
    return 0;
}
int* changeArr(int arr[],int arrSize)
{
    int* arrCopyPointer;
    arrCopyPointer=new int[arrSize];
    for(int i=0;i<arrSize;i++){
        arrCopyPointer[i]=arr[i]+1;
    }
    return arrCopyPointer;
}