编程随想录
CSDN拆迁户 @2014-04-07

导航

 

Index:
    (new) 指针和数组的区别
    (一) 指针的类型, 指针指向的类型.
    (二) 指针的加减运算.
    (三) 指针与数组.
    (四) 数组做函数的形参.
    (五) 函数指针.
    (六) 指针与数组/数组做函数形参.
    (七) C-Style的字符串.
    (八) 复杂指针的定义.
    (九)C语言中没有引用!
   

(new) 指针和数组的区别: http://coolshell.cn/articles/11377.html
指针和数组的区别不仅仅是"指针p定义后可以改变其值, 而数组a[]一旦定义后无法改变a的值";
先看下面的代码, 在哪一行会coredown ?
#include <stdio.h>
struct str{
    int len;
    char s[0];
};

struct foo {
    struct str *a;
};

int main(int argc, char** argv) {
    struct foo f={0};
    if (f.a->s) {
        printf("%x\n", f.a->s);
        printf( f.a->s);
    }
    return 0;
}



答案是在printf( f.a->s) 挂掉, printf("%x\n", f.a->s)会打印出4,
如果代码中的struct str结构体中的char s[0];改成char *s, 那么代码在if(f.a->s)处就挂掉了, why ?

if (f.a->s)   // char* s;
if (f.a->s)   // char s[0];
以上两种有什么不同?
用GDB查看汇编代码后发现,:
a. 对于char s[0]来说,汇编代码用了lea指令,lea   0×04(%rax),   %rdx
b. 对于char*s来说,汇编代码用了mov指令,mov 0×04(%rax),   %rdx
lea全称load effective address,是把地址放进去,而mov则是把地址里的内容放进去。所以,就crash了。
从这里,我们可以看到,访问成员数组名其实得到的是数组的相对地址,而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的)

# 对于数组 char s[10]来说,数组名 s 和 &s 都是一样的.
char a[10] = {0};
char (p)[10] = &a;
printf("%x %x\n", a, p);  // 你会发现两个值是一样的



代码二, 哪一行会挂掉?
struct test{
    int i;
    short c;
    char *p;
    char s[10];
};
int main(){
    struct test *pt=NULL;
    printf("&s = %x\n", pt->s); //等价于 printf("%x\n", &(pt->s) );
    printf("&i = %x\n", &pt->i); //因为操作符优先级,我没有写成&(pt->i)
    printf("&c = %x\n", &pt->c);
    printf("&p = %x\n", &pt->p);
    return 0;
}



分析: 都不会挂掉. 因为都是lea指令.

#重要:
(1) 不管结构体的实例是什么——访问其成员其实就是加成员的偏移量,
(2) int array[], 数组名array和&array是一样的.
(3) 仔细分析, 那些代码是lea, 哪些是mov ?  对于char *s,  if(pt->s)和 printf(pt->s)和 printf("%s",pt->s)都是mov指令(需要代码验证);
而printf("%x",pt->s)是lea指令, 不会因Cannot access memory挂掉.


代码三:
char a[] = "hello";
char *p = "world";
数组a被定义后, a即为数组名, 其值不能再改变, 而指针p的值可以改变;
在代码中使用a[3]时, 直接从&a[0] 向后寻找3个字节并取出那个字节; 而编译器看到p[3]时, 会先生成代码找到p的位置, 取出其中的指针值, 在指针值上+3再取出该字节.
a[3]和p[3], 编译器解释不同, 取出字节的方式也不同. 换言之, a[3]是名为a的对象(起始位置)之后的第3个字节, p[3]是p指向的对象向后的第3个字节.



(一)指针的类型, 指针指向的类型:
  例子,说明以下指针的类型/指向的类型:  
  (1)int* ptr;  
  (2)char* ptr;  
  (3)int** ptr;  
  (4)int (*ptr)[3];  
  (5)int* (*ptr)[4];


将*号去掉, 剩下的部分即是"指针的类型";
将"指针名字"和"指针名字左边的指针声明符*"去掉, 剩下的部分即是"指向的类型".
#指针的值就是一个内存地址, 或者说,指针指向的内存区的开始地址, 指针的长度为sizeof(int)=4,
#sizeof(指针)=4, 无论指针指向数组,浮点数,类或其他类型, sizeof无法分辨"指针指向的类型".


(二) 指针的加减运算
  加减运算包括: 自++,--, 指针±数值, 指针±指针, 这些运算时, 指针数值的变化, 指针指向的变化.
  自++/--: 指针±1, 指针数值±sizeof(指针类型), 指针指向下一个元素的地址;
  指针±N: 指针数值±sizeof(指针类型)*N, 指针移动到后面第N个元素;
  指针-指针: 两个指针相隔的元素个数, 同类型的指针才可以相减, 比如“(int*)p1 - (int*)p2”的结果, 与“(char*)p1 - (char*)p2”不同;
  指针+指针: 无意义.

阅读下面的代码, 打印结果?:
int a[5]={1,2,3,4,5};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int )a+1);
printf("%x,%x",ptr1[-1],*ptr2);

第二行: &a, 数组名取地址, 相当于一个"数组指针", 该指针的类型是int (*)[5], 指向的类型是int [5], 所以这个指针+N, 指针实际移动的字节数 = N * sizeof(int [5]),
ptr1[-1]会打印出: 5,
结论: 设p是一个指针, (int*)p+1, (char*)p+1, (int)p+1, 的结果?

 
(三) 指针与数组
  (3.0)数组做函数形参:
    int func(const int a[], int size); int func(const int *p, int size);两个声明相同.


    int data[3][4] = {{1,2,3,4},{...},{...},{...}};
    接受二维数组data[][]做形参的函数:
   void func(int(*ar)[4], int size);
   void func(int ar[][4], int size);
   #以上两个原型都正确, 都明确指出形参ar是指针而不是数组.


  (3.1)指针与数组的区别:
    指针可以++/--, 数组名不可以++/--.


  (3.2)数组指针与指针数组:
     int* p[10];   // 指针数组, 数组的元素是int*
     int (*p)[10]; // 数组指针, p指向的类型是int[10]
    或者:
     typedef int ARRAY [10];
     ARRAY* p; // 数组指针, p指向的类型是int[10]
    #设有int array[5], array即代表数组首地址, array, &array, array[0],&array[0],array+i分别代表什么?
   
   #数组指针,解操作(*)一次, 得到的类型是什么?  解一次到的类型即p指向的类型, 按照前面的语法分析, 指向的类型是int [10] ,即上面定义的ARRAY类型, 但这种类型太抽象, 无法实际应用. 实际使用中, 一维数组, 二维数组, 数组指针对应的类型?



#二维指针如何正确对二维数组进行访问?
int array[2][5] = {{0,1,2,3,4}, {5,6,7,8,9}};
int **p = (int**)array;
cout<< *(*(p+1) + 2) <<endl; // 试图访问a[1][2], 出错;
出错原因在p+1试图访问array[ ][ ]的第二行,如果要访问第二行指针需移动5个int的偏移字节,但二级指针p++后移动的字节数是一个指针的长度。

修改如下:
cout<< *(*((int(*)[5])p+1) + 2) <<endl; // 正确

分析: 这样修改后, p的类型是int**, 而array的类型是int(*)[5], int**类型+1, int(*)[5]类型+1的值不一样.





(五) 函数指针:
   typedef  void  (*pf1)(char*,int);
   typedef  int  (*pf2)(void);
   通过指针执行函数: pf1(ptr,2);


(七) C-Style的字符串:
    char* s1 = "12345";
    char s2[] = "12345";
    字符串s1和s2所占的内存空间都是6, 结尾的'\0'也占一个字节. 右值双引号方式定义的数组, 结尾都自动添加'\0'.
    所以strlen()计算s1和s2的长度都是4,
    sizeof(s1) =4, sizeof(s2) =6.

    char *pc ,str[100];
    str="String..."; // 错,字符数组名str不能直接赋值
    pc="String...."; // 对,指针变量pc可以直接赋字符串地址
    字符数组只能对其元素逐个赋值,而不能将字符串赋给字符数组名。对于字符指针变量,字符串地址可直接赋给字符指针变量。例如:

  *能否在定义时只有:char str[];但不给数组定义大小?

  在程序执行期间,字符数组名表示的起始地址是不能改变的,而指针变量的值是可以改变的。
  char str[ ] ="...String..." ;
  char *pc    ="...String..." ;
  str=str+5; // 错误
  pc=str+5; // 正确

C语言中字符串与数组的赋值:
char* p=new char[6];
p="Hello"; // error!!
strcpy(p,"Hello"); // 正确的做法
cout<<*p<<endl; //输出字符串的第一个字符
cout<<p<<endl;   //输出p指向的字符串

(八) 复杂指针的定义:
float   (**def)[10];
double   *(*gh)[10];
double   (*f[10])();
int   *((*b)[10]);

//--右左法则--
首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。原文如下:
The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.



复杂指针类型分析的例子:

   float   (**def)[10];
   def是一个指向数组的二级指针,其一级部分所指向的数组的元素是具有10个float元素的数组。

   double   *(*gh)[10];
   gh是一个指向数组的指针,所指向的数组的元素是具有10个double类型指针元素的数组
   常见的错误:gh是一个指向数组的指针,所指向的数组是具有10个double类型指针元素的数组

   double   (*f[10])();
   f是一个函数指针数组,这些函数指针所指向的函数返回值为double。

   int   *((*b)[10]);
   这个声明(*b)[10]外部的()是多余的,它跟int   *(*b)[10]声明是一样的。b是一个指向数组的指针,这个数组的元素是具有10个int类型指针元素的数组。


   double   *(*gh)[10];
   double   *a[5][10];

   来说,gh与a相同,而不是与a[i]相同,a[i]才是一个指向具有10个double类型指针元素的数组的地址。


复杂指针定义总结:
1. 指针指向的类型,把变量名和最近的*号去掉,就是指向的类型,比如int *ptr,int **ptr[10];
2. 在定义语句中, 后缀符有()和[]两种, 分别代表函数和数组, 前缀符*, 代表这是一个指针,
3. 注意"指针定义语句"中的括号, 中括号, 以及"*号"的位置,
   a,靠近"类型"的*号,如果没有括号做分隔说明, 此*号则是靠近类型的,比如int *ptr[10],定义了一个大小为10的数组,元素类型是"int*".
   b,对于二级指针的定义,必须两个*号之间没有被括号分隔,并且没有a中的情况,比如int **ptr[10]和int (**ptr)[10]的区别.
   c,靠近[]的变量名,如果此变量名和[]之间没有括号分隔,此变量名则是靠近[]的,意即"指向数组的指针".
   d,如果[]符号和靠近[]的变量名,它们之间有括号分隔, 则是"由指针为元素组成的数组".



posted on 2011-04-22 08:43  dos5gw  阅读(173)  评论(0编辑  收藏  举报