zoukankan      html  css  js  c++  java
  • C指针系列二

    一、函数调用——传址

          函数调用中将数组作为参数进行传递,此时,它和指针的关系变得很微妙。我们应该要弄明白调用时到底传递的是什么。首先分析下面的简单代码:

     1 int array[5] = {1, 2, 3, 4, 5};
     2 void print( int a[] ) {
     3     int i = 0;
     4     for(; i < sizeof(a)/sizeof(a[0]); ++i)
     5         printf(“%d  ”, a[i]);
     6 }
     7 int main()
     8 {
     9     print(array);
    10     return 0;
    11 }

      调用print(array)时打印出来的结果是1(而我们的预想结果是1 2 3 4 5), n = sizeof(a)/sizeof(a[0])是一种常用的表示数组元素个数的方法。之所以没有打印预料的结果,是因为将数组作为实参调用函数时,所做的工作仅仅是在栈中开辟一个4字节的空间,用来存放数组首元素地址的副本。故此时的a仅是个指向array[0]的指针,sizeof(a) /sizeof(a[0])=1,ok…问题解释完毕!!!

      刚才啰嗦的半天得到的结论是:数组作为实参传递时,传递的只是一个指针(数组首元素地址的副本),不管你的函数形式是如下哪一种:

            void print( int a[] )    void print( int *a )---------两种形式等价

    这就决定了我们在C/C++中传递数组时必须带上一个n(表示数组元素个数),故函数print正确的形式是void print( int *a, int n)或void print(int a[], int n)。

    谈到这里我们马上想到了一个例外——如果我们想将一个字符串中的所有字符转换为大写的(这里假设字符串的字符全是小写字符),代码应该是这样:

    1 void UpperCase( char str[] ){
    2     int i = 0;
    3     while( str[i] != ‘\0’ )
    4         str[i++] -= (‘a’-‘A’);
    5 } 

      这里没有添加一个形参n去表示字符串长度,难道传递的是整个数组而不是指针???其实,这里传递的也只是字符数组首元素的地址而已,之所以不需要指定数组的长度,是因为字符数组有个结尾标识符’\0’,即隐含的传递了数组大小的信息。基于这个思想我们可以修改上面的情况——假设要打印出5个童鞋的英语成绩(不可能为负,故添加-1作为结束符):

    1 int array[5] = {1, 2, 3, 4, 5, -1};//末尾添加了一个标识
    2 void print( int a[] ) {//这里不用传递数组大小n
    3     int i = 0;
    4     while( a[i] != -1)                
    5             printf(“%d  ”, a[i]);
    6 }

      上面谈到的情形——将数组作为实参进行传递的时候,具体传递了什么——虽然不复杂,但还是应该弄清楚的好,至少它体现了C/C++设计思想的一个很小的部分。java和pyhton舍弃了指针,用引用替代之;java中的数组或是python中的列表都被视为对象,即它们都对数组或列表进行了封装,并提供了一些有用的属性(如数组的长度信息),加上以引用的方式进行传递,使得操作更加便利(至少不需要传递一个表示数组大小的参数)。C++中的数组是内置型,操作方式和C中的一样,故大家更倾向于使用vector。

    二、指针的应用 

    (1)指针数组

       下面先列出几个看起来有点复杂的声明,再聊聊指针数组及多级指针

              int *a[5];//声明a为指针数组

            int **b[5];//声明b为指针数组

                          int ***c[5];//声明c为指针数组

       a,b和c都是指针数组,只是数组元素不同;a中的元素是指向int型变量的指针,b中的元素是指向int*型变量的指针,同理,c中的元素是指向int**型变量的指针。谈到二级指针或是更高级的指针时,很抽象的赶脚啊!!!其实多级指针没有多维数组来的抽象,不管是多少级的指针,它都只是一个4个字节的变量,用来存放其它变量的地址而已,不要将其视为一个多么可怕的潘多拉盒子。下面的赋值完全正确:

               int ***p = NULL;

               int *pi = &p;//ok…编译器最多就是给出一个警告,不想看到的警告的话就强制性转换吧!!!

          一点也不神秘嘛。。。只要让类型匹配(逃过编译器的法眼),指针随你怎么操作,前提是你的脑子里面必须有张很清晰的内存分布图。下面摆出一个动态创建二维数组的模板程序(更高维的依此类推)

     1 int main()
     2 {
     3     int **p = NULL;//记得初始化,Don’t be a slacker!!!
     4     int i, j;
     5     int m, n;
     6     scanf(“%d %d”, &m, &n);//动态创建m*n二维数组
     7     if((p = (int **)malloc( sizeof(int*)*m )) != NULL){//作为一个合格的程序员不要忘了这步!!!
     8         for(i=0; i<m; ++i){
     9             if((p[i] = (int*)malloc( sizeof(int)*n))!= NULL){
    10                 memset(p[i], 0, sizeof(int)*n);//如果最后的结果全是垃圾数据时应该想起这步!!!
    11             }
    12         }
    13       for(i=0; i<m; ++i){
    14           for(j=0; j<n; ++j)
    15               scanf("%d", &p[i][j]);
    16       }
    17       //具体的数据处理操作
    18       //...
    19       for(i=0; i<m; ++i)
    20           free(p[i]);//忘了这步真是活该没有妹子要!!!
    21       free(p);
    22     }
    23     return  0;
    24 }

    (2)数组指针

      先摆上一个程序题,来引出后面的话题:

    1 #include <stdio.h>
    2 int main( void )
    3 {
    4     int a[5][10];
    5     printf(“%d  %d\n”, a+1, &a+1 );//假设a的值是1310000!!!
    6     return  0;
    7 }

      暂且搁置一个这个题目,下面来讨论数组指针和二维数组的关系,了解这层关系之后,使用二维数组就会容易的多。。。就从数组指针的声明形式开始吧!!!

                int (*a)[5];//声明a为数组指针

                int p[5][5];//作对比之用

     两者都是定义了元素个数为5的数组(每个元素又是一个数组),可以将a和p视为等价, 看看下面两者的关系图。

                               

      要弄明白一个指针指向的数据类型,最好的办法是对其进行加减操作。a进行加1操作跳过的字节数=sizeof(int)*5, a加1跳过的是一个元素个数为5的数组耶,故a是一个数组指针,指向的是一个数组而不是一个int型变量;同样的方法来分析&p, p和*p。三者的值是一样的(存放的是相同的地址),但是三者代表的含义不同。&p加1操作后跳过的字节数=sizeof(int)*5*5,表示&p指向的是一个5行5列的int型二维数组;而p加1操作后跳过的字节数=sizeof(int)*5,表示p指向的是一个元素个数为5的int型一维数组(和a一样);*p加1后跳过的字节数=sizeof(int),表示*p指向的是一个int型变量。同样的数据类型可以直接赋值咯!!!ok…下面这么写绝对没有错:

        int (*a)[5];

        int p[5][5];

        a = p;//都是指向一个拥有5个元素的int型数组

    到这里就可以轻松的将上面那道程序题解出来了。。。。

       遵循上面谈到的内容,考虑二维数组作为实参进行传递的时候,传递的是什么东东???传递的也是p的一个副本(也可以说是*p或&p的值的一个副本,因为三者的值都相同),告诉起始地址还不够,必须加上元素个数n,是一维的个数还是二维的个数呢???先看看下面代码之后在继续闹吧…

     1 #include <stdio.h>
     2 void print( int (*a)[5], int n ){//写成print( int a[][], int m, int n)行不???
     3     int i, j;
     4     for(i=0; i<n; ++i){
     5         for(j=0; j<5; ++j)
     6             printf("%d ", a[i][j]);
     7         printf("\n");
     8     }
     9 }
    10 int main()
    11 {
    12     int p[2][5] = {{1,2,3,4,5}, {6,7,8,9,10}};
    13     print(p, 2);
    14     return 0;
    15 }

     

  • 相关阅读:
    一种不求交点确定直线与三角形是否相交的方法
    碰撞边界锯齿的平滑方法
    demo的凹凸贴图效果
    MySQL进阶篇触发器
    MySQL进阶篇索引
    Maven的POM文件详解
    Swagger
    MySQL进阶篇存储过程
    SpringBoot基础篇
    MySQL基础篇多表操作
  • 原文地址:https://www.cnblogs.com/Happyhe/p/3118985.html
Copyright © 2011-2022 走看看