C语言之数组与指针的易混淆知识点

一、指针与数组


指针:指针本身也是一个变量,它的内容是指向的内容的地址。指针同样有类型的区分,char 的指针只能指向char型数据,int 指针指向int型数据。但是指针所占内存单元的大小(即其内容)是跟操作系统的地址位数有关,比如32位地址的系统,那么指针所占的内存单元就是4个字节,16位就是2个字节,因此,指针的类型只是限定它所指向的变量的类型,其内容所占的单元大小是与操作系统的地址位数相关,与变量类型无关。 
在32位地址系统中,比如:

int  a = 10;   //int型一般长度为4字节,所以sizeof(a)的值为4
int * p =a;    // p中存放的是a的地址,所以sizeof(p)的长度是4字节
char b = 'c';  // char型一般长度为1个字节,所以sizeof(b)值为1
char * d = c;  // d中存放的依然是a的地址,所以sizeof(d)值为
               // 4(而不是 1 !)

注意:void * 指针是未确定的指针,它可以指向任何类型的变量。

数组:数组是一个连续占有一段内存单元的变量集合。 
比如:

  int a[10]; // a:数组名,代表的是当前数组所在的首地址。
  a = 3;     // 错误;数组名a不是一个变量,它是一个地址常量,因
             // 此对于a的赋值或自增是会报错的;
             // a[0]:表示的是当前首地址下存储的内容;
             //&a[0]:表示第一个元素的地址,这时候与a是一样,即首地址

指针与数组区别

1、指针是一个单独变量,只是指向了其他变量的地址(相当于汇编中的间接寻址,与地址寄存器类似); 
数组是一串元素序列并且真实的存储元素内容,它的数组名可以相当一个指针(与指针的用法“基本”一样),代表数组的首地址; 
比如:

int a[10]; //系统实实在在分配了10*4个字节的连续内存单元
int* p=a; //也可以写成 int* p = &a[0];p指向数组a[10]的首地址a[0];
          //p只是一个变量,只占据4个内存单元,存储的是数组a的首地址

其实:p=p[0]=a[0]=*a;(p+i)=p[i]=a[i]=*(a+i);指针和数 
组的取值可以互换。

2、 sizeof对于指针变量和数组的处理是不一样的。拿上面的指针p 和数组a[10]来说,对于一个32位地址的系统。

 sizeof(p)的值为  4 个字节;
 sizeof(a)的值为  40 个字节;

原因是因为,p是一个指针变量其内容存储的是4个字节的地址;而数组名a并不是一个变量,它是一个常量的地址,sizeof将其视为整个数组的代表,因此计算的时候会计算整个数组的大小。 
但是下面的情况又会不同:

void computesize(int *a,int b[], int c[10]);
int main(){
    int a[10];
    computesize(a,a,a);
}
void computesize(int *a,int b[],int c[10]){
    printf("a = %d , b = %d , c = %d",sizeof(a), sizeof(b),sizeof(c));               
}

这时执行strlen(a,a,a); 假设是32位地址系统。 
输出的值并不是4、40、40,而是4、4、4,那是不是有矛盾了呢?并不是的,因为这牵扯到了另一个知识点——函数参数的传递。我们知道,传递参数有值传递和地址传递。上面的情况就属于地址传递,无论形参是*a、b[]还是c[10],传入的时候都是将首地址传给指针变量a 、b、c。b与c的不同就是b没有指定长度,而c指定了长度。这时候其实a、b、c相当于一个指针变量而不是之前的数组了,否则每一次传递都要重新弄一个副本,系统会吃不消的。


二、指针与函数


1、指针与函数参数:指针和数组作为参数传递的时候,其实是传递的一个地址。 
比如:

int main(){
int b[10];
int* a = b;
printf(" %d ",sizeof(b));//输出40
printf(" %d ",sizeof(a));//输出4
fun(a,b);
}

void fun(int *a,int b[]){
   printf(" %d ",sizeof(b));//输出4
   printf(" %d ",sizeof(a));//输出4
};

传进去的都是一个地址变量,sizeof计算出来的大小是一样的(但是在定义他们的地方sizeof的长度是不一样的)。

2、字符指针与函数:(重点,容易忽略)

//定义一个数组,内存中只有数组,存放在堆栈中(注意,此时字符串不是
//常量字符串,不在静态区而是在数组所在的堆栈内存中)。
char aMeg[] = "I am a boy.";  

//定义一个指针变量,内存中具有一个指针变量和一个字符串常量,并且字
//符串常量存放在静态区中,指针在堆栈中分配。
char * pMeg = "I am a boy.";  

  


三、指针数组、数组指针、指向指针的指针


1、指针数组: 
形如:int * p[10]; //一个指针数组,数组里面有10个元素,每一个元素都是一个int型的指针 
数组内的每一个元素都是一个指针变量(这时注意每个元素所占的内存单元大小是地址的长度而不是类型长度)

2、数组指针形如:int (* b)[10];//一个数组指针,指针指向一个列长度为十的一个二维数组的第一行的行地址。 
也称作行指针,该指针指向了一个长度为10的数组的行首地址; 
如:b表示第一行的首地址;b+1表示第二行首地址

 *(b) = b[0][0];  *(b+1) = b[1][0]; *(*(b+i)+j) = b[i][j]

3、指向指针的指针:形如: int **c = p;//二级指针c,指向了指针数组p的首地址&p[0],即指向了指针数组的第一个指针的地址 
该指针大小也是取决于操作系统,它跟一级指针其实本质上是没有差别的,只是说是有连环指向的这种感觉。 
c是指向指针的指针,因此它使用间接取址符时需要两次才能取出目标的内容。 
比如:*c表示的是p[0]的地址,而**c表示的是p[0]地址中的内容

 c == &p[0];  // 指针数组中第一个指针元素的地址
 *c == p[0];  // 指针数组中第一个指针元素地址的内容,即目标变量的地址
 **c == *p[0]; // 指针数组中第一个指针元素指向的目标变量值

  


四、程序陷阱


1、*与++运算符的优先级相同,而且都是右结合的(一元都是的)。

 a = *p++ :表示先将指针p自增,然后再取指针的内容赋值给a;
 a =(*p)++: 表示先取指针内容赋值给a,然后指针再自增

2、/、*、%等二元运算符具有相同的优先级,而且结合性是自左向右的。

  a = 1/2*a: 因为是自左向右结合的,故表示的是 0.5*a,然后再赋值给a。
  a = 1/(2*a): 这才表示求2*a的倒数,然后赋值给a。

3、字符串数组和指针 
当指针指向字符串常量时,通过指针是不能修改字符串常量的值的。如:

   char * p1 = "Hello World";
   p1[5] = ',';   //错误,这是一种C语言标准未定义的操作

对于p1[5] = ‘,’;不同系统会给出不同结果,在Turbo C环境下,可能会完成赋值过程,但是对于像VC++、Dev-C++来说这是一个错误的操作,因为“Hello World”是一个字符串常量,存储在常量区,从C语言的概念和定义上讲,是没有这个标准的。

4、利用malloc分配内存 
如果想将某一个字符串复制到某一个控件,一定要记得分配足够的空间大小,不要忘记””结束符。 比如:

 char * strSrc = "Hello Boy.";
 char * strDest;
 //错误,strlen并未计算""结束符,赋值后的指针末尾指向未知空间。
 strDest = (char *) malloc(strlen(strSrc));

 //正确,为""留出空间。
 strDest = (char *) malloc(strlen(strSrc)+1); 
 strcpy(strDest,strSrc);

5、空指针和空字符串的差别 
空指针是指向0(NULL)的指针,C语言保证对空指针的操作是安全的。如下:

char * p = NULL;   // #define NULL 0   ,这是C语言定义的NULL

而空字符串则是一个只有一个’’结束符的字符串,它在内存中是有存储空间的。比如:

char p[] ="";//之战一个字符空间,
char p1[] = "";//与上面不同,这里占据两个字符空间,
char a[10];
printf("%d , %d 
",sizeof(p),strlen(p));  //输出为1,0 说明占有一个字节空间,注意此时strlen(p)为0,
printf("%d , %d 
",sizeof(p1),strlen(p1)); //输出为2,0  说明占有两个字节空间,注意此时strlen(p1)为0!

6、strlen与sizeof的区别

  • sizeof: 
    1、计算所有变量类型的占用字节大小 
    2、在计算字符串的时候,会将字符串后面的’’的大小也计算上来。 
    3、计算的是字节内存的大小 
    4、计算数组名的时候特殊,会计算整个数组的长度。其他的均计算单个变量

  • strlen: 
    1、计算的是字符串的长度大小 
    3、计算字符串长度时,将会忽略’’结束符,遇到’’字符就会结束。

7、使用未初始化的指针

  int x,*p;
  x=10;
  *p = x;  // 错误,p并未指向一个确定的地方,它并没有被初始化。

8、NULL指针 

NULL指针并不指向任何对象,在赋值和比较运算意外的其他运算符都是非法的。由于标准并未对NULL指针赋值运算、比较运算意外的运算进行定义,因此这些运算都将得到一个不确定的结果。有时候可能给系统带来灾难性的后果。 如:

  int *p = NULL;
  int x = 10;
  int a = *p * x;  
  int b = *p * x;
  printf("%d  %d",a,b);  //在 Dev 上运行出错

  

原文地址:https://www.cnblogs.com/cthon/p/9199706.html