记录数组指针与指针数组的易混淆点

来源:互联网 发布:淘宝联盟要认证怎么弄 编辑:程序博客网 时间:2024/05/20 20:21

这两天看Linux设备模型的kobject结构,整个人都不好了,老是搞错一些东西~感觉还是有必要好好在复习一下基础知识。

1、指针数组与数组指针

这两个名字不同当然所代表的意思也就不同。我刚开始看到这就吓到了,主要是中文太博大精深了,整这样的简称太专业了,把人都绕晕了。从英文解释或中文全称看就比较容易理解。

指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针

数组指针:a pointer to an array,即指向数组的指针

还要注意的是他们用法的区别,下面举例说明。

int* a[4]     指针数组     

                 表示:数组a中的元素都为int型指针    

                 元素表示:*a[i]   *(a[i])是一样的,因为[]优先级高于*

int (*a)[4]   数组指针     

                 表示:指向数组a的指针

                 元素表示:(*a)[i]  

注意:在实际应用中,对于指针数组,我们经常这样使用:

1
2
typedef int* pInt;
pInt a[4];

这跟上面指针数组定义所表达的意思是一样的,只不过采取了类型变换。

1.1 int 类型

<span style="font-family:Microsoft YaHei;">#include <iostream> using namespace std; int main(){int c[4]={1,2,3,4};int *a[4]; //指针数组int (*b)[4]; //数组指针b=&c;//将数组c中元素赋给数组afor(int i=0;i<4;i++){a[i]=&c[i];}//输出看下结果cout<<*a[1]<<endl; //输出2就对cout<<(*b)[2]<<endl; //输出3就对return 0;}</span>

注意:定义了数组指针,该指针指向这个数组的首地址,必须给指针指定一个地址,容易犯的错得就是,不给b地址,直接用(*b)[i]=c[i]给数组b中元素赋值,这时数组指针不知道指向哪里,调试时可能没错,但运行时肯定出现问题,使用指针时要注意这个问题。但为什么a就不用给他地址呢,a的元素是指针,实际上for循环内已经给数组a中元素指定地址了。但若在for循环内写*a[i]=c[i],这同样会出问题总之一句话,定义了指针一定要知道指针指向哪里,不然要悲剧。

1.2 char 类型

声明 char *ponitArray[] = {"stately" , "plump" , "buck" , "mulligan"}; 

由定义知这是一个指针数组,那么sizeof(pointArray)=?呢,

因为pointArray是一个存放指针的数组,而存放指针其实存放的是地址,一般用4个字节表示,而数组大小为4,故而结果就为4*4 = 16.
 

  1. char (*arrayPoint)[4];  
  2. char t[4]="123";  
  3. arrayPoint=&t;  
  4. cout<<"*A = "<<*arrayPoint<<"\tA = "<<arrayPoint<<endl; 

上面几行代码输出结果应该是什么呢?,首先arrayPoint是一个指针,指向一个存放4个字符的C风格字符串,从这里我们可以知道arrayPoint其实是一个32位(一般)的整数,所有下一步我们要将一个长度为4的字符串地址赋给该指针,即arrayPoint=&t;,故可知输出结果就为“123”和字符串t的地址。

1.3 小结

数组指针:数组名本身就是一个指针,指向数组的首地址。注意这是声明定长数组时,其数组名指向的数组首地址是常量。而声明数组并使某个指针指向其值指向某个数组的地址(不一定是首地址),指针取值可以改变。
例如:int (*p)[10] 表示一个指向10个int元素的数组的一个针。   

指针数组 : 一个数组,若其元素均为指针类型数据,称为指针数组。 也就是说,指针数组中每一个元素都相当于一个指针变量。其详细形式应该如下: *a[0], ...*a[n]. 每一个数组里面存储的是其指向的地址;一维指针数组的定义形式为:类型名 *数组名[数组长度]
例如:int *p[4],由于[]比*优先级更高,因此p先与[4]结合,形成p[4]的形式,这显然是数组形式。然后再与p前面的*结合,*表示此数组是指针类型的,每个数组元素都指向一个整型变量

2、函数指针与指针函数

函数指针:指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
 

struct file_operations {int (*seek) (struct inode * ,struct file *, off_t ,int);int (*read) (struct inode * ,struct file *, char ,int);int (*write) (struct inode * ,struct file *, off_t ,int);int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);int (*select) (struct inode * ,struct file *, int ,select_table *);int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);int (*open) (struct inode * ,struct file *);int (*release) (struct inode * ,struct file *);int (*fsync) (struct inode * ,struct file *);int (*fasync) (struct inode * ,struct file *,int);int (*check_media_change) (struct inode * ,struct file *);int (*revalidate) (dev_t dev);}

上面这个结构体file_operations里面的组件都是函数指针:int (*read) (struct inode * struct file * char int);注意int (*read)是加上括号的

指针函数:首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。

float *fun();float *p;p = fun(a);

3、面试题

①下面就一道面试题,看一下指针与数组的区别。

char *p1, *p2;char ch[12];char **pp;p1 = ch;pp = &ch;p2 = *pp;问p1和p2是否相同

首先,数组ch是没有初始化的。其次,一个比较隐含的地方是,数组名可以代表数组第一个元素的首地址,这个没有问题,但是,数组名并非一个变量,数组分配完成后,数组名就是固定的,地址也是固定的。这样导致的结果就是绝对不能把数组名当作变量来进行处理。上述题目中,pp=&ch,显然是把数组名当作指针变量来使用了,这样肯定出问题。

这个题目存在的两个问题,第一个问题比较简单,可以认为是粗心大意。但是第二个问题就是相当复杂了,扩展开来,那就是C语言中的精华中的指针和数组的联系与区别问题了。

http://www.cppblog.com/beautykingdom/archive/2008/08/06/58105.aspx

②&a+1 与 a+1的区别

#include<stdio.h>#include<stdlib.h>int main(void) {     int a[5]={1,2,3,4,5};     int *ptr=(int *)(&a+1);      printf("%d,%d",*(a+1),*(ptr-1));    return 0;}
  1. &a+i = a + i*sizeof(a);  
  2. a+i = a +i*sizeof(a[0]);

所以输出 2,5

指针的类型

http://blog.sina.com.cn/s/blog_4aca89270100dxr6.html

先声明几个指针放着做例子:例一:(1)int *ptr;(2)char *ptr;(3)int **ptr;(4)int (*ptr)[3];(5)int*(*ptr)[4];1.指针的类型从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:(1)int *ptr;//指针的类型是int*(2)char *ptr;//指针的类型是char*(3)int **ptr;//指针的类型是int**(4)int (*ptr)[3];//指针的类型是int(*)[3](5)int *(*ptr)[4];//指针的类型是int*(*)[4]怎么样?找出指针的类型的方法是不是很简单?2.指针所指向的类型当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:(1)int *ptr; //指针所指向的类型是int(2)char *ptr; //指针所指向的的类型是char(3)int **ptr; //指针所指向的的类型是int*(4)int (*ptr)[3]; //指针所指向的的类型是int()[3](5)int *(*ptr)[4]; //指针所指向的的类型是int*()[4]在指针的算术运算中,指针所指向的类型有很大的作用。指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的”类型”这个概念分成”指针的类型”和”指针所指向的类型”两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。


下面摘自http://blog.csdn.net/deep_explore/article/details/6261929

1. 常指针与常量的指针
       char * const p;
  char const * p
  const char *p
  上述三个有什么区别?

  char * const p;   //p为只读指针。
  char const * p;//p值只读的指针。
  const char *p; //和char const *p一样

---------------------------------------------------
2.定义与声明
声明是普通的声明:它所描述的并非自身,而是描述在其他地方创建的对象。
定义是特殊的声明:它为对象分配内存,即现场创建对象。

---------------------------------------------------
3.左值与右值
 I.左值:编译时可知。左值可以分为可修改左值和不可修改左值,数组名就是一个不可修改的左值。即不能给数组名赋值!(注意这里指的是“数组名”) 这是为什么呢?下面有一个现场:
char a[10] = {'a'};
char b[10] = {'b'};
a = b;
编译时出现的错误:
error: incompatible types when assigning to type ‘char[10]’ from type ‘char *’ 
结论:
当数组名为左值时,它的类型是字符数组;当数组名为右值时,它的数据类型是字符指针。

 II.右值:运行时可知。
---------------------------------------------------

4.指针与数组
 如果编译器需要一个地址来执行一种操作,对于被定义的数组变量而言的,它地址在编译时可知,所以它就可以直接进行操作;而对于被声明的指针而言,只有在程序运行的时候才知道它所指向的地址的值,然后才能在当前地址上操作。

 char array[] = “sdfsdf”;   …   c = a[i];
 数组的下标引用步骤:(1)取编译器符号中的符号array的地址(假设是8888)
         (2)array[i]即为取地址8888+i的内容。
 char *p; … c = *p;
 指针的间接引用步骤:(1)取编译器符号表中符号p的地址(假设是7777)
         (2)获取7777位置处的内容(假设是9999)
         (3)取9999处地址的内容,即为*p
 
 char *p = “abcdefg”;
 char a[] =“abcdefg”;
那么,p[i]与a[i]的区别?
a[i]是直接在(符号表a的地址+i)处获取内容,即为直接引用。
p[i]是先获取符号表p地址的内容,然后在该内容上+i地址处获取内容,即为间接引用。这里的“间接”指的是要被操作的地址不能直接从编译器符号表中直接获得,而是从指针对象中获得。


     char str1[] = "abc";
  char str2[] = "abc";
  const char str3[] = "abc";
  const char str4[] = "abc";

  const char *str5 = "abc";
  const char *str6 = "abc";

  char *str7 = "abc";
  char *str8 = "abc";

  cout << ( str1 == str2 ) << endl;
  cout << ( str3 == str4 ) << endl;
  cout << ( str5 == str6 ) << endl;
  cout << ( str7 == str8 ) << endl;

 

   打印结果是什么?

解答:结果是:0 0 1 1

str1,str2,str3,str4是数组名,各表示一个数组变量(即对象变量),它们有各自的内存空间;而str5,str6,str7,str8是指针变量,它们指向相同的常量区域。不同的对象之间当然不可能相等,而指向相同区域的指针变量是相等的。

-----------------------------------------------

5. sizeof()函数

以下代码中的两个sizeof用法有问题吗?
void UpperCase( char str[] ){ // 将 str 中的小写字母转换成大写字母 
 int i; 
 for( i = 0; i < sizeof(str)/sizeof(str[0]); ++i ){ 
 if(str[i] >= 'a' && str[i] <= 'z') 
           str[i] -= ('a'-'A' ); 
 }

}

int main(){ 
 char str[] = "aBcDe"; 
 printf("str字符长度为: %lu/n", sizeof(str)/sizeof(str[0])); 
 UpperCase( str ); 
 printf("%s/n", str); 
}

 

答:函数内的sizeof有问题。

根据语法,sizeof只能测出定义的对象,不能测出声明的变量。静态数组是定义的对象,可以测出;而在函数内,str只是一个声明的变量,非定义的对象,故不能测出。
在gcc4.4.5/x86_64-linux-gnu环境下有,
sizeof(char*)=8
sizeof(int*)=8
所以,这里可以得到正确的结果,但是有时候,可能出现数组越界的情况。


-------------------------------------------------

 

6.C不进行数组 的下标检查,地址可以越界,间接访问的时候可能出现“段错误”:
 int a[5]={1,2,3,4,5}; 
 int *ptr=(int *)(&a+1); 
 printf("%d,%d/n",*(a+1),*(ptr-1));
 输出结果是什么?

  答案:输出:2,5
  *(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
  &a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int)
  &a是数组指针,其类型为 int (*)[5];

--------------------------------------------

 

7.strcpy()函数:

int main(){ 
 char a; 
 char *str=&a; 
 strcpy(str,"hello"); 
 printf("%s/n", str); 
}
  答案:没有为str分配内存空间,将会发生异常。问题出在将一个字符串复制进一个字符变量指针所指地址。虽然可以正确输出结果,但因为越界进行内在读写而导致程序崩溃。

---------------------------------------------

 

8.指针的间接引用

分析下面代码的问题:
        char* s="AAA";
   printf("%s",s);
   s[0]='B';
   printf("%s",s);
  
分析:对象s的内容,是一个常量字符串“AAA”内存空间的首地址。s[0]='B'这一句,表示给一个字符常量赋值,显然是不合法的。


---------------------------------------------

9. int (*s[10])(int) 表示的是什么?

  答案:int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函数。

---------------------------------------------

10. 传值函数,对主函数中的变量没有影响
void getmemory(char *p){ 
 p=(char *) malloc(100); 
 strcpy(p,"hello world"); 

int main(){ 
 char *str=NULL; 
 getmemory(str); 
 printf("%s",str); 
 printf("/n");   
 free(str); 
 return 0; 
}
在getmemory()函数中,参数p充当一个行参。在主函数中,free()操作一个空指针,不会起任何作用。


------------------------------------------

 

11.要对绝对地址0x100000赋值,我们可以用*((unsigned int*)0x100000) = 1234;

   那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?

   这里必须要注意:有方法分配指定区域的内存,是给绝对地址赋值的前提条件。纠结的是,C没有提供分配指定区域内存的方法。
  答案:*((void (*)( ))0x100000 ) ( );

  首先要将0x100000强制转换成函数指针,即:

  (void (*)())0x100000
  然后再调用它:
  *((void (*)())0x100000)();
  用typedef可以看得更直观些:
  typedef void(*)() voidFuncPtr;
  *((voidFuncPtr)0x100000)();

------------------------------------------

 

12. 分析下面的程序:
void GetMemory(char **p,int num){                     
 *p = malloc(num); 
}  
int main() 

 char *str=NULL;

 GetMemory(&str,100);  
 strcpy(str,"hello"); 
 free(str); 
 if(str!=NULL){ 
  printf("%X/n", str); 
 }   
}
  问输出结果是什么?
  答案:输出的是指针对象str的值。在个指针分配内存的时候,其实给指针对象赋了一个值,这个值就是这片空间的首地址。释放空间后,这个指针对象的值,其实是指向没有被分配空间的地址。访问这样的越界空间,编译是没有问题的,但是运行时编译器出现“Segmentation fault”.

-------------------------------------------

 

13.分析下面程序的结果:

int main() 

 char a[10]; 
 printf("%d",strlen(a)); 
}

答案:0,这由strlen()函数的内部实现有关。
--------------------------------------------

 

14.char (*str)[20];

  char *str[20];

---------------------------------------------

 

15.分析下面代码:

typedef struct AA{ 
 int b1 : 5; 
 int b2 : 2; 
}AA;

void main() 

 AA aa; 
 printf("%d/n", 'A'); 
 char cc[100]; 
 strcpy(cc,"0123456789abcdefghijklmnopqrstuvwxyz"); 
 memcpy(&aa,cc,sizeof(AA));//将sizeof(AA)个连续的字节空间(从cc开始),源和目的地不能重叠 
 printf("%d %d/n", aa.b1, aa.b2);

}

  首先sizeof(AA)的大小为4,b1和b2分别占5bit和2bit.经过strcpy和memcpy后,aa的4个字节所存放的值是: 0,1,2,3的ASC码,即00110000,00110001,00110010,00110011所以,最后一步:显示的是这4个字节的前5位,和之后的2位分别为:10000,和01,因为int是有正负之分



1 0