二维数组和指向指针的指针 ,指针做形参做局部变量以及内存分配

一道面试题引发的问题,首先要知道[]的优先级高于*,题目:

char **p,a[6][8]; 问p=a是否会导致程序在以后出现问题?为什么?

直接用程序说明:


#include<stdio.h>

void main()
{
    char **p,a[6][8];
    p = a;
    printf("\n");
}

编译,然后就会发现通不过,报错:错误 1 error C2440: “=”: 无法从“char [6][8]”转换为“char **” 

于是乎,我看了下《C专家编程》里10.5节—使用指针向函数传递一个多维数组。

方法一,函数是 void fun(int arr[2][3]); 这种方法只能处理2行3列的int型数组。

方法二,可以省略第一维的长度。函数是 void fun(int arr[][3]);这种方式虽然限制宽松了一些,但是还是只能处理每行是3个整数长度的数组。

    或者写成这种形式 void fun(int (*arr)[3]);这是一个数组指针或者叫行指针,arr和*先结合使得arr成为一个指针,这个指针指向具有3个

    int类型数据的数组。

方法三,创建一个一维数组,数组中的元素是指向其他东西的指针,也即二级指针。函数是 int fun(int **arr);这种方法可以动态处理各行各列不一样长度的数据。

注意:只有把二维数组改成一个指向向量的指针数组的前提下才可以这么做!比如下面的程序可以正常输出abc:


#include <iostream> 
using namespace std; 
void test(char **ptr) 
{ 
    cout << *ptr << endl; 
} 
 
int main() 
{ 
    char *p[3] = {"abc", "def", "ghi"}; 
    test(p); 
    return 0; 
}

在《C专家编程》10.3节的小启发里讲的很透彻:(以下这段文字及对比一定要认真分析!)

数组和指针参数是如何被编译器修改的?

数组名被改写成一个指针参数”规则并不是递归定义的。数组的数组会被改写成“数组的指针”,而不是“指针的指针”:

实参                                                      所匹配的形参

数组的数组          char c[8][10];                 char (*)[10];          数组指针

指针数组             char *c[10];                   char **c;               指针的指针

数组指针(行指针)  char (*c)[10];                 char (*c)[10];        不改变

指针的指针           char **c;                       char **c;               不改变

下面再看一个网友的一段分析相当给力的代码:


#include "stdafx.h" 
#include <iostream> 
using namespace std; 
 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int arr1[3]; 
    int arr2[3]; 
    int arr3[3]; 
    int * ptr; 
    // ptr1是一个指向 int [3] 的指针,即ptr的类型和&arr1的类型是一样的,注意:arr1指向的内存区域定长 
    int ptr1[3][3]={{1,2,3},{1,2,3},{1,2,3}}; 
    // ptr2是一个指向 int * 的指针,即ptr2的类型和&ptr是一样的,注意:ptr指向的内存区域不定长 
    int * ptr2[3]={arr1,arr2,arr3}; 
    // ptr3是一个指向 int [3] 的指针,即ptr3的类型和&arr1的类型是一样的,注意:arr1指向的内存区域定长 
    int(* ptr3)[3]=&arr1; 
    ptr3=ptr1; // 没错,他们的类型相同 
 // ptr3=ptr2;//error 无法从“int *[3]”转换为“int (*)[3] 
 // ptr4是一个指向 int * 的指针,即ptr4的类型和&ptr是一样的,注意:ptr指向的内存区域不定长 
    int ** ptr4; 
    //ptr4=&arr1; //error 无法从“int (*)[3]”转换为“int ** 
    ptr4=ptr2; // 没错,他们的类型相同 
 //ptr4=ptr3; // error 无法从“int (*)[3]”转换为“int ** 
    return 0; 
  
  

————————————————————————————————————————————————————————————————————————

指针做形参做局部变量以及内存分配

一级指针做形参:首先一定要明白形参和你传递参数的那个实参是两个不同的变量,即使同名也还依然不同。指针传递的是一个变量或者一个值的地址,但是它本身还是采用值传递的方式。即你不能使它指向另外一块地址,但是你可以改变它指向的空间里存的值。

二级指针做形参:二级指针也是传值,但是他指向的地址是个一维指针,所以可以改变二维指针指向的地址空间里的内容也就是要申请空间的一维指针,不能改变二维指针本身的值,即不能让他指向一个新的一维指针。所以二维指针传递的是一个一维指针。

具体看下面这个程序以及输出:

复制代码
#include<stdio.h>
#include <malloc.h>
#include <string.h>

void GetMemory1(char *p)
{
    //该函数的形参为字符指针,在函数内部修改形参并不能真正改变传入形参的实参值。
 //因此在主函数中执行完下面两句之后
 //char *str1 = NULL; 
 //GetMemory1(str1);
 //str1仍然为NULL,因此会报内存错误,没有输出结果。
    p = (char *)malloc(100);

    //要记得使用指针变量时,每次分配空间后要判断是否分配成功。而且在主函数中使用之后记得释放内存,并置空
    if (p == NULL)
    {
        printf("error memory");
    }
}
/*但是上面的函数参数变为*char *&p就可以在主函数中正常输出了。
指针做形参也是采用值传递的方式,也就是会把指针的值-地址传过去,所以可以改变这个地址里的内容,
但是你不能改变指针的值,也就是指向的地址。但是引用就是变量的别名,所以可以改变指针的值,
所以就可以在函数里申请空间*/

char *GetMemory2(void)
{
    char p[] = "hello world";
    return p;    //p[]数组为函数内部局部变量,在函数返回后,内存已经被释放了,
 //所以在主函数中调用该函数str2 = GetMemory2();输出的可能为乱码
}

void GetMemory3(char **p,int num)
{
    *p = (char *)malloc(num);

    //要记得使用指针变量时,每次分配空间后要判断是否分配成功。而且在主函数中使用之后记得释放内存,并置空
    if (*p == NULL)
    {
        printf("error memory");
    }
}
void main()
{
    int n=0;
    char *str1 = NULL;    
    char *str2 = NULL;
    char *str3 = NULL;

    //GetMemory1(str1);
 //strcpy(str1,"Hello world");
 //printf("%s\n",str1);

    str2 = GetMemory2();
    printf("%s\n",str2);//输出乱码

    GetMemory3(&str3,100);
    strcpy(str3,"hello world");
    printf("%s\n",str3);//输出hello world

    char *str4 = (char *)malloc(100);
    if (str4 == NULL)
    {
        printf("error memory");
    }
    else
    {
        strcpy(str4,"Hello");
        free(str4);
        str4 == NULL;//free后要置空,否则str可能变成“野指针”
    }
    if (str4 != NULL)
    {
        strcpy(str4,"world");
        printf("%s\n",str4);//输出world
    }
    scanf("%d",&n);
}
复制代码

指针做局部变量:如果你申请了空间(用new等,赋值不算)又没有delete,那么这个空间在你程序运行结束之前不会释放,只要你知道这个空间的地址,就可以访问。这里的赋值不算是指,比如,你先定义一个数组,然后把数组名赋值指针。但是char *d = "ZET";这种形式相当于new了4个空间。

下面是中兴通讯2012校招笔试题目,问输出什么?

当时答错(狂汗),现在搞明白,在函数里写了注释:

复制代码
#include <stdio.h>

//此函数中d也是个局部变量,函数执行完自动销毁,但是指针分配的空间不会被自动回收,除非程序员delete掉。
//所以这个可以正常输出。
char *a()
{
    char *d = "ZET";//这个初始化的一种形式,相当于分配了四个空间
    return d;
}

//但是第二个数组空间是系统维护的,函数执行完自动销毁 
char *b()
{
    char p[10] = {"3G平台"};
    return p;
}


//参数是值传递方式,改变形参的地址,传递的实参的地址确不会因此改变 
void c(int n,char *pName)
{
    char *a[4] = {"aaa","bbb","ccc","ddd"};
    pName = a[n];
}

void main()
{
    int n=0;
    char *pName = "DB";
    printf("%s\n",a());//输出ZET
    printf("%s\n",b());//随机输出乱码

    c(2,pName);
    printf("%s\n",pName);    //输出DB,因为char *pName = "DB";已经使得pName指向了DB,但c(2,pName);并不能改变pName指向的地址。
 //形象点说就是:我有一个箱子给你用,你可以在里面装东西,但是你不能把我的箱子换成另外一个给我。
 //在这里指的是不能函数调用不能使pName变成函数中的二维数组a。
    
    scanf("%d",&n);
}
复制代码

指针做形参,指针做局部变量,数组做形参,数组做局部变量之类的题目非常容易考到,而且容易迷糊,得不断学习...


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值