C语言指针与数组的定义与声明易错分析

部分摘自《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类型数据。

C语言指针与数组的定义与声明易错分析

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

指针数组与数组指针

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

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

[]比*优先级高

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

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

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

C语言指针与数组的定义与声明易错分析

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
}

C语言指针与数组的定义与声明易错分析

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

警告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;

C语言指针与数组的定义与声明易错分析

一个例题:

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)

C语言指针与数组的定义与声明易错分析 

对于为什么会出现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)

C语言指针与数组的定义与声明易错分析

数组参数与指针参数

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]的首地址

C语言指针与数组的定义与声明易错分析

函数指针与函数指针数组

下面转自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;
}

  

上一篇:[Visual studio] Visual studio 2017添加引用时报错未能正确加载ReferenceManagerPackage包的解决方法


下一篇:Linux下mysql新建账号及权限设置