部分摘自《C语言深度解剖》

1.定义为数组,声明为指针

在文件1中定义:

char a[100];

在文件2中声明:

extern char *a;  //这样是错误的

这里的extern告诉编译器a这个名字已经在别的文件中被定义了,下面的代码使用的a是在别的文件中定义的。编译器是按文件分别编译的,当a被声明为char* a时,编译器理所当然的认为a是一个指针变量,在32位系统下占用4个byte,这4个byte存放的是地址,地址指向的空间存储的是char类型数据。

程序会返回SIGSEGV。

2.定义为指针,声明为数组

文件1:char* p="abcdefg";

文件2:extern char p[];  //这样也是错误的

在文件1中,编译器分配4个byte空间,并命名为p。同时p里面保存了字符串常量“abcdefg”的首字符地址。这个字符串常量本身保存在内存的静态区,其内容不可修改。在文件2中,编译器认为p是一个数组,其大小为4byte,数组保存的是char类型数据。

总结:代码在一个地方定义为指针,在别的地方也只能声明为指针;同理数组。

指针数组与数组指针

指针数组:首先它是一个数组,数组的元素都是指针。

数组指针:首先它是一个指针,指针指向一个数组。

[]比*优先级高

A) int *p1[10]; => (int *)p1[10]; 即它首先是一个数组,数组的元素都是int*;

B) int (*p2)[10];  首先它是一个指针,指针指向一个包含10个元素的数组;

A:指针数组 B:数组指针

main()
{
char a[5]={'A','B','C','D'};
char (*p3)[5]=&a; //数组指针
char (*p4)[5]=a; //会产生警告:从不兼容的指针类型初始化
char (*p5)[3]=&a; //waring
char (*p6)[3]=a; //waring
char (*p7)[10]=&a; //waring
char (*p8)[10]=a; //waring
}

对上面五个警告做下分析:

警告1:左值为元素个数为5的数组指针,但是右值是指向元素第一个元素首地址的指针,类型不符;

警告2:左值为元素个数为3的数组指针,但是右值是指向含5个元素的整个数组的首地址,类型不符;

警告3:左值为元素个数为3的数组指针,但是右值是指向含5个元素的数组第一个元素的首地址,类型不符;

警告3/4同2/3。

然后分析下打印出来的值:

由于左右值类型不兼容,从右值赋给左值之前先对右值进行了左值类型的强制转换。

然后就容易理解:

为什么p4+1的值是0xbffff2ba,而不是0xbffff2b6(未强制转换前); 强制转换后a指向整个数组的首地址,与&a相同;

为什么p5+1的值是0xbffff2b8,而不是0xbffff2ba;强制转换后&a由指向含5个元素的整个数组的首地址变为指向含3个元素的整个数组的首地址。

为什么p6+1的值是0xbffff2b8,而不是0xbffff2b6;这里是前面两种强制转换的综合结果。

后面雷同。

地址的强制类型转换

先看个实例

struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;

假设p的值为0x1000000。

p+0x1=?

(unsigned long)p+0x1=?

(unsigned int*)p+0x1=?

实质上是看编译器怎么处理指针变量和一个整数相加减。类似前面a+1和&a+1的区别。

指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte而是元素的个数;

看强制类型转换博客中关于运算符的部分:http://www.cnblogs.com/kwseeker-bolgs/p/4488907.html

所以:

1. p+0x1=0x1000000+sizeof(Test)*0x1=0x1000014;

关于为什么这里的sizeof(Test)=20,后面详细介绍。

2. (unsigned long)p+0x1=?这里涉及强制类型转换,将struct类型转换为unsigned long,所以两者现在都是无符号长整型,直接相加:

(unsigned long)p+0x1=0x1000000+0x1=0x1000001;

3. (unsigned int*)p+0x1=?前者是无符号整形,后者也需要转换为无符号整形:

(unsigned int*)p+0x1=0x1000000+sizeof(unsigned int)*0x1=0x1000004;

4. (unsinged int)p+0x1=0x1000000+0x1=0x1000001;

这里要非常注意(unsinged int)与(unsinged int*)的区别,一个是按无符号整形数据处理,一个是按指针处理。

调试结果如下:这里的p=0x0;

一个例题:

int main()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);    
int *ptr2=(int *)((int)a+1); printf("%x, %x", ptr1[-1],*ptr2);  //输出4,2000000
return 0;
}

分析:ptr1[-1] 解析为*(ptr1-1)

 

对于为什么会出现2000000,首先需要弄清系统大小端存储模式。

int checkSystem()
{
union check
{
int i;
char ch;
}
c.i=1;
return(c.ch==1);
}
//返回0为大端模式,返回1为小端模式

经过验证系统采用小端模式。所以*ptr2=0x02000000。

二维数组与二级指针

二维数组

二维数组char a[3][4]中a[i][j]元素的首地址是:a+i*sizeof(char)*4+j*sizeof(char),指针形式*(*(a+i)+j)。

一个陷阱题:

#include <stdio.h>
int main()
{
int a[3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a[0];
printf("%d",p[0]);
}

读题时很容易忽略大括号里面嵌套的不是大括号而是小括号(即逗号表达式)。

上面的其实是int a[3][2]={1,3,5}={{1,3},{5,0},{0,0}};

int a[5][5];

int (*p)[4];

p=a;

问&p[4][2]-&a[4][2]的值是多少?

关键问题是int (*p)[4] 到 &p[4][2]是怎样的一个过程?

根据定义:p是一个指向包含4个元素的数组的指针。也就是说p+1表示的是指针p向后移动一个"包含4个int类型元素的数组",这里1的单位是4*sizeof(int)。所以p[4]相对于p[0]来说是向后移动了4*(4*sizeof(int))。由于p被初始化为&a[0],那么& p[4][2]=&a[0][0]+4*4*sizeof(int)+2*sizeof(int)。(0x72-0x88bytes)

数组参数与指针参数

1. 不能向函数以数组的形式传递一个数组,实参只能以指针的形式传递数组。但是函数定义的时候形参可以是数组形式。

C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

#include <stdio.h>

void func(char a[10]){
char c=a[3];
}
void func1(char *p){
char c=p[3]; //或者 char c = *(p+3);
}
void func2(char a[]){
char c=a[3];
} int main(int argc, char const *argv[])
{
char b[10]="abcdefg";
//func(b[10]); //此处的b[10]并不代表一个数组,而是一个越界的数组元素
func(b);         //后面三个都是可以成功传值的
func1(b);
func2(b); return 0;
}

  

2.一级指针参数

1)能否把指针变量本身传递给一个函数

#include <stdio.h>

void func(char *p){
char c=p[3]; //或者 char c = *(p+3);
} int main(int argc, char const *argv[])
{
char *p2="abcdefg";
func(p2);  //这样操作没有语法错误,但是此句所做的操作只是对拷贝做的,在main中没有意义 return 0;
}

  

二维数组参数与二维指针参数

void fun(char a[3][4]); 还可写成 void fun(char a[][4]);

由上面的规则可以改写为

void fun(char (*p)[4]);  //p指向a[3]的首地址

函数指针与函数指针数组

下面转自http://patmusing.blog.163.com/blog/static/1358349602010182102157/

什么是函数指针?

函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分

一个具体函数的名字,如果后面不跟调用符号(即括号),则该名字就是该函数的指针(注意:大部分情况下,可以这么认为,但这种说法并不很严格)。

函数指针的声明方法

// 定义函数指针pf

int (*pf)(const int&, const int&);                                                (1)

上面的pf就是一个函数指针,指向所有返回类型为int,并带有两个const int&参数的函数。注意*pf两边的括号是必须的,否则上面的定义就变成了:

int *pf(const int&, const int&);                                                   (2)

而这声明了一个函数pf,其返回类型为int *, 带有两个const int&参数。

typedef定义函数指针类型

// 定义函数指针类型cmpFun

typedef int (*cmpFun)(const int&, const int&);                      (3)

或许这样写更容易理解 typedef  int (*)(const int&, const int&)  cmpFun;

这样,cmpFun就成了一种数据类型,可以用它来声明和定义形如(1)式中的pf那样的函数指针,比如:

cmpFun pf = 0;

cmpFun pf = someFunction;

举个例子来说明一下:

#include <iostream>
#include <string>
using namespace std; // 定义函数指针pf
int (*pf)(const int&, const int&); // int (*)(const int&, const int&) pf; // 定义函数指针类型cmpFun
typedef int (*cmpFun)(const int&, const int&); // 具体函数
int intCompare(const int& aInt, const int& bInt)
{
if(aInt == bInt) return 0;
if(aInt > bInt)
{
return 1;
}
else
{
return -1;
}
} int main(void)
{
int aInt = 1;
int bInt = 2;
pf = intCompare;
// pf = &stringCompare; // 和上面一句是完全一样的
// 使用pf
if(pf(aInt, bInt) == 0)
{
cout << "two integers are equal" << "." << endl;
}
else if(pf(aInt, bInt) > 0)
{
cout << aInt << " is greater than " << bInt << "." << endl;
}
else
{
cout << aInt << " is less than " << bInt << "." << endl;
} cout << "------------------------" << endl;
// 用函数指针类型cmpFun声明并初始化一个函数指针pf2
cmpFun pf2 = intCompare;
// 使用pf2
if(pf2(aInt, bInt) == 0)
{
cout << "two integers are equal" << "." << endl;
}
else if(pf(aInt, bInt) > 0)
{
cout << aInt << " is greater than " << bInt << "." << endl;
}
else
{
cout << aInt << " is less than " << bInt << "." << endl;
} return 0;
}

函数指针作为参数

函数指针可以作为一个函数的参数,如下两种办法可以做到这一点:

(a) int plusFun(int&, int&, int (const int&, const int&));

(b) int plusFun(int&, int(*)(const int&, const int&));

以上两个方式做到的是类似的事情:(a)中的plusFun函数的第三个参数就是一个函数指针, (b)中的第二个参数也是一个函数指针。下面我们分别定义前面声明的两个plusFun函数。

(a)中的plusFun定义如下:

//函数指针作为参数:错误的做法

//int plusFun(int& aInt, int& bInt, int paf(const int& cInt, const int& dInt))

//{

//

//       return aInt + bInt + paf(cInt, dInt);

//}

//函数指针作为参数:正确的做法

int plusFun(int& aInt, int& bInt, int paf(const int &, const int &))

{

int cInt = 2;

int dInt = 1;

return aInt + bInt + paf(cInt, dInt);

}

调用plusFun的代码:

pf = intCompare;

// 函数指针作为参数

int aaInt = 3;

int bbInt = 4;

cout << plusFun(aaInt, bbInt, pf) << endl;

(b)中的plusFun定义如下:

//函数指针作为参数:错误的做法

//int plusFun(int& aInt, int(*paf2)(const int& bInt, const int& cInt))

//{

//       return aInt + paf2(bInt, cInt);

//}

//函数指针作为参数:正确的做法

int plusFun(int& aInt, int(*paf2)(const int&, const int&))

{

int bInt = 1;

int cInt = 2;

return aInt + paf2(bInt, cInt);

}

调用plusFun的代码:

cmpFun pf2 = intCompare;

// 函数指针作为参数

int aaInt = 3;

cout << plusFun(aaInt, pf2) << endl;

函数指针作为返回值

一个函数的返回值可以是一个函数指针,这个声明形式写起来有点麻烦:

// 函数指针作为返回值

int (*retFunPointer(int))(const int&, const int&);

上面的声明的含义:

a)       retFunPointer是一个函数,该函数有一个int类型的参数;

b)       retFunPointer返回值是一个函数指针,它指向的是带有两个const int&类型参数,且返回类型为int的函数。

retFunPointer的定义:

// 函数指针为返回值

int (*retFunPointer(int aInt))(const int&, const int&)

{

cout << aInt << endl;

// pf已经在前面定义过了

return pf;

}

调用代码示例:

// 函数指针作为返回值,retFunPointer返回一个cmpFun类型的函数指针

cmpFun pf3 = retFunPointer(aaInt);

int result = pf3(aaInt, bbInt);

cout << result << endl;

包含上面所有情况的完整代码

#include <iostream>
#include <string>
using namespace std; // 定义函数指针pf
int (*pf)(const int&, const int&); // 定义函数指针类型cmpFun
typedef int (*cmpFun)(const int&, const int&); // 函数指针作为参数
int plusFun(int&, int(const int&, const int&));
int plusFun(int&, int(*)(const int&, const int&)); // 函数指针作为返回值
int (*retFunPointer(int))(const int&, const int&); // 具体函数
int intCompare(const int& aInt, const int& bInt)
{
if(aInt == bInt) return 0;
if(aInt > bInt)
{
return 1;
}
else
{
return -1;
}
} //函数指针作为参数:错误的做法
//int plusFun(int& aInt, int& bInt, int paf(const int& cInt, const int& dInt))
//{
//
// return aInt + bInt + paf(cInt, dInt);
//} //函数指针作为参数:正确的做法
int plusFun(int& aInt, int& bInt, int paf(const int &, const int &))
{
int cInt = 2;
int dInt = 1;
return aInt + bInt + paf(cInt, dInt);
} //函数指针作为参数:错误的做法
//int plusFun(int& aInt, int(*paf2)(const int& bInt, const int& cInt))
//{
// return aInt + paf2(bInt, cInt);
//} //函数指针作为参数:正确的做法
int plusFun(int& aInt, int(*paf2)(const int&, const int&))
{
int bInt = 1;
int cInt = 2;
return aInt + paf2(bInt, cInt);
} // 函数指针为返回值
int (*retFunPointer(int aInt))(const int&, const int&)
{
cout << aInt << endl;
// pf已经在前面定义过了
return pf;
} int main(void)
{
int aInt = 1;
int bInt = 2;
pf = intCompare;
// pf = &stringCompare; // 和上面一句是完全一样的
// 使用pf
if(pf(aInt, bInt) == 0)
{
cout << "two integers are equal" << "." << endl;
}
else if(pf(aInt, bInt) > 0)
{
cout << aInt << " is greater than " << bInt << "." << endl;
}
else
{
cout << aInt << " is less than " << bInt << "." << endl;
} cout << "------------------------" << endl;
// 用函数指针类型cmpFun声明并初始化一个函数指针pf2
cmpFun pf2 = intCompare;
// 使用pf2
if(pf2(aInt, bInt) == 0)
{
cout << "two integers are equal" << "." << endl;
}
else if(pf(aInt, bInt) > 0)
{
cout << aInt << " is greater than " << bInt << "." << endl;
}
else
{
cout << aInt << " is less than " << bInt << "." << endl;
}
cout << "------------------------" << endl; // 函数指针作为参数
int aaInt = 3;
int bbInt = 4;
cout << plusFun(aaInt, bbInt, pf) << endl;
cout << plusFun(aaInt, pf2) << endl;
cout << "------------------------" << endl; // 函数指针作为返回值,retFunPointer返回一个cmpFun类型的函数指针
cmpFun pf3 = retFunPointer(aaInt);
int result = pf3(aaInt, bbInt);
cout << result << endl; return 0;
}

  

C语言指针与数组的定义与声明易错分析的更多相关文章

  1. C语言指针与数组

    C语言指针与数组 数组的下标应该从0还是1开始? 我提议的妥协方案是0.5,可惜他们未予认真考虑便一口回绝    -- Stan Kelly-Bootle   1. 数组并非指针 为什么很多人会认为指 ...

  2. C语言指针和数组知识总结(上)

    C语言指针和数组知识总结(上) 一.指针的基础 1.C语言中,变量的值能够通过指针来改变,打印指针的语句符号可以是:  %08x 2.指针的本质 指针的本质就是变量,那么既然是变量,那么一定会分配地址 ...

  3. C语言指针和数组

    #include <stdio.h> int main() { /********************************************* * * 指针和数组: * 定义 ...

  4. 11-C语言指针&一维数组&字符串

    一.用指针遍历数组元素 1.最普通的遍历方式是用数组下标来遍历元素 1 // 定义一个int类型的数组 2 int a[4] = {1, 2, 3, 4}; 3 4 int i; 5 for (i = ...

  5. c语言指针,数组

    指针:说简单点就是一个地址.例如int *p,p是个变量,里面放的是地址0x0000,同理,每一个指针,不管什么类型,都是地址,也就是空间都是4个字节(32位机). 以此类推,指针也有指向它的指针in ...

  6. C语言中的指针与数组的定义与使用

    指针的特点 他就是内存中的一个地址 指针本身运算 指针所指向的内容是可以操作的 操作系统是如何管理内存的 栈空间 4M~8m的大小 当进入函数的时候会进行压栈数据 堆空间 4g的大小 1g是操作系统 ...

  7. c语言 指针与数组

    关键概念: 1.多个不同类型的指针可以对应同一个地址: 2.(&p)则是这样一种运算,返回一个指针,该指针的值是当时声明p 时开辟的地址,指针的类型是p的类型对应的指针类型: 3.(*p)操作 ...

  8. C语言指针和数组知识总结(下)

    一.数组指针: 数组指针就是一个指针,只不过它指向的是一个数组.可以通过如下方式来定义 typedef int Array[5]; //数组类型 Array* m;      //数组定义 还有一种更 ...

  9. 大一C语言学习笔记(5)---函数篇-定义函数需要了解注意的地方;定义函数的易错点;详细说明函数的每个组合部分的功能及注意事项

    博主学习C语言是通过B站上的<郝斌C语言自学教程>,对于C语言初学者来说,我认为郝斌真的是在全网C语言学习课程中讲的最全面,到位的一个,这个不是真不是博主我吹他哈,大家可以去B站去看看,C ...

随机推荐

  1. VMware-workstation-full-10.0.1-1379776 CN

    从V10版本开始,VMware Workstation 官方自带简体中文了,以后大家不需要汉化啦! 今天,VMware Workstation 10.0.1正式发布,版本号为Build 1379776 ...

  2. Allow windows service to "Interact with desktop"

    Typically, services are designed to run unattended without any UI with any need to interact with des ...

  3. iOS对象序列化

    系统对象的归档我就不介绍了,这个不复杂,自己看一下就会了. 我在这里主要介绍自定义对象的归档. Sample.h文件 // //  Sample.h //  Serialization // //   ...

  4. IOS导航栏的使用方法

    本文是使用纯代码实现一个导航栏的效果.单击按钮并且产生事件.基本思路是: 1.创建一个导航栏(UINavigationBar对象) 2.创建一个导航栏集合(UINavigationItem对象) 3. ...

  5. hdu 2070

    ps:...递推..还是给出公式那种... 代码: #include "stdio.h" #define LL long long LL dp[]; int main(){ int ...

  6. 让Tomcat支持中文文件名

    --参考链接:http://blog.chinaunix.net/uid-26284395-id-3044132.html 解决问题的核心在于修改Tomcat的配置,在Server.xml文件中添加一 ...

  7. Tomcat容器运行struts2+spring+mybatis架构的java web应用程序简单分析

    1.具体的环境为 MyEclipse 8.5以及自带的tomcat spring3.0.5 struts2.3.15.1 mybatis3.0.5 2.想弄明白的一些问题 tomcat集成spring ...

  8. linux常用命令:2权限管理命令

    权限管理命令 1.权限管理命令:chmod 命令名:chmod 命令英文原意:change the permissions mode of a file 命令所在路径:/bin/chmod 执行权限: ...

  9. excel表里的数据导入到数据库里

    采用的是jxl,所以需要导jxl-2.4.2.jar的jar包.(前提知道excel表的目录): //用log记录异常信息 private static final Logger log = Logg ...

  10. [转】:HTTP请求流程(一)----流程简介

    http://www.cnblogs.com/stg609/archive/2008/07/06/1236966.html HTTP请求流程(一)----流程简介 最近一直在研究如何让asp.net实 ...