open cv+C++错误及经验总结(十三)

数组指针与指针数组的用法与区别:

一、指针数组:

数组元素全为指针数组称为指针数组

int *ap[n]; []优先级高,先与ap结合成为一个数组,再由int *说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行ap+1时,则ap指向下一个数组元素。如有定义int a[n][m];这样赋值是错误的:ap=a;因为ap是个不可知的表示,只存在ap[0]、ap[1]、ap[2]...ap[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 

*ap = *a;  ap[0] = *a; ap[0] = a[0];*ap[0] = *a[0];这里*ap=ap[0];a[0] = *a = *a[0]表示指针数组第一个元素的值,*a的首地址的值(因为ap是一维指针数组,它指向的也必须是一维数组地址,*a代表二维数组第0行首地址)。

int *ap[4];//一维指针数组//
	 int a[4][2] = {1,2,3,4,5,6,7,8};
	 ap[0] = *a;
	 ap[1] = *(a+1);
	 ap[2] = *(a+2);
	 ap[3] = *(a+3);
	 
	 cout<<*(ap[0]+ 0)<<'\t'<<*(ap[0]+ 1)<<endl;
	 cout<<*(ap[1]+ 0)<<'\t'<<*(ap[1]+ 1)<<endl;
	 cout<<*(ap[2]+ 0)<<'\t'<<*(ap[2]+ 1)<<endl;
	 cout<<*(ap[3]+ 0)<<'\t'<<*(ap[3]+ 1)<<endl;

输出:

自己写了一个实例:

 int *ap[4];//一维指针数组//
 
	 for(int i = 0; i  < 4; i++) //开辟空间//
	 {
		 ap[i] = (int *) malloc(sizeof(int) * 2);
	 }

	 for (int i = 0;i < 4 ;i++ ) //变量赋值//
	 {
		*(ap[i]+ 0) = 1 * i + 1+i;
		*(ap[i]+ 1) = 1 * i + 2+i;	 
	 }	 

	 cout<<*(ap[0]+ 0)<<'\t'<<*(ap[0]+ 1)<<endl;
	 cout<<*(ap[1]+ 0)<<'\t'<<*(ap[1]+ 1)<<endl;
	 cout<<*(ap[2]+ 0)<<'\t'<<*(ap[2]+ 1)<<endl;
	 cout<<*(ap[3]+ 0)<<'\t'<<*(ap[3]+ 1)<<endl;
	 
	 for(int i = 0; i  < 4; i++) //释放空间//
	 {
		 free(ap[i]);
	 }
输出:

注:指针数组中的元素亦可以表示为“*(*(ap+i))”。又因为“()”的优先级较“*”高,且“*”是右结合的,因此可以写作**(ap+i)。

由于数组元素均为指针,因此ap[i]是指第i+1个元素的指针。

用法:指针数组可以作为函数的参量使用,使用方式与普通数组类似。指针数组常适用于指向若干字符串,这样使字符串处理更加灵活方便。

指针数组通常用于字符串数组的使用;

char *chp[3] = {"adf","adfg","afaid"};
	 cout<<chp[0]<<endl;
	 cout<<chp[1]<<endl;
	 cout<<chp[2]<<endl;
输出:

二、数组指针:数组指针也称行指针

int (*ap)[n];()优先级高,首先说明ap是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是ap的步长。也就是说执行ap+1时,ap要跨过n个整型数据的长度如要将二维数组赋给一指针,应这样赋值:

int a[m][n],(*ap)[n];  

ap = a;  

ap++;

现在我们讨论另外一种写法:让其动态的开辟内存空间

int (*ap)[4]; //二级指针
	 ap = new int[2][4];
	
	 for (int i = 0;i < 2 ;i++ ) //变量赋值//
	 {
		 *(ap[i]+ 0) = 4*i+1;
		 *(ap[i]+ 1) = 4*i +2;
		 *(ap[i]+ 2) = 4*i+3;
		 *(ap[i]+ 3) = 4*i +4;
	 }

	 cout<<*(ap[0]+ 0)<<'\t'<<*(ap[0]+ 1)<<endl;
	 cout<<*(ap[0]+ 2)<<'\t'<<*(ap[0]+ 3)<<endl;
	 cout<<*(ap[1]+ 0)<<'\t'<<*(ap[1]+ 1)<<endl;
	 cout<<*(ap[1]+ 2)<<'\t'<<*(ap[1]+ 3)<<endl;
	 delete []ap;
输出:

int (*ap)[4] = new int[2][4]; 注意,这里的ap指向了一个二维int型数组的首地址.

注意:在这里,ap等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的元素数量必须要指定!

就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。这与数组的嵌套定义相一致。

继续列举例子:

 int (*ap)[2]; //数组指针
	 int abb[2] = {2,3};
	 ap = &abb;

	 cout<<(*ap)<<endl;
	 cout<<(*ap+1)<<endl;
     cout<<ap<<endl;
	 cout<<abb<<'\t'<<&abb<<endl;
注: (*ap)和 (*ap+1)是存放数值2和3的地址值。

不可将ap = abb,会报错error C2440: '=' : cannot convert from 'int [2]' to 'int (*)[2]',所然他们的地址值相同,但意义不同。abb是数组首元素的首地址,&abb是数组首地址。

输出:

 int (*ap)[2]; //数组指针
	 int abb[2] = {2,3};//这里相当于0行两列的数组,即abb[0][2]
	 ap = &abb;//&abb是数组的首地址ap[0] = abb[0],ap[1] = abb[1]

	 cout<<*(*(ap+0)+0)<<endl;
	 cout<<*(*(ap+0)+1)<<endl;

注:改成 cout<<*(*(ap+0)+0)<<endl;等价于cout<<*((*ap+0)+0)<<endl; 等价于 cout<<*(*ap)<<endl;
    cout<<*( *(ap+0)+1)<<endl; 等价于 cout<<*((*ap+0)+1)<<endl; 等价于 cout<<*(*ap+1)<<endl; 

也会输出同样的结果,但不建议中间的写法。

(*ap)和(*ap+1)是存放数值2和3的地址值。

输出

int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[3] = &a;
   char (*p4)[3] = a;
   return 0;
}
分析上面的代码他们编译时都会报错: 

error C2440: '=' : cannot convert from 'char (*)[5]' to 'char (*)[3]'

error C2440: '=' : cannot convert from 'char [5]' to 'char (*)[3]'

三、指针数组与数组指针的内存布局

初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。

参考:http://www.cnblogs.com/mq0036/p/3382732.html  好好看看

http://blog.csdn.net/lg2lh/article/details/7229980

内存对齐http://blog.csdn.net/liukun321/article/details/6974282 重点看指针变量在结构体分配的空间。

四、地址的强制转换

先看下面这个例子:
struct Test
{
   int Num;
   char *pcName;
   short sDate;
   char cha[2];
   short sBa[4];
}*p;

假设p 的值为0x100000。如下表表达式的值分别为多少?
   p + 0x1 = 0x___ ?
   (unsigned long)p + 0x1 = 0x___?
   (unsigned int*)p + 0x1 = 0x___?
我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?

还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。至于此结构体的大小为20byte,前面的章节已经详细讲解过。所以p +0x1 的值为:0x100014。

(unsigned long)p + 0x1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。

(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。 这的内容不太清楚???

上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:在x86 系统下,其值为多少?
intmain()
{
   int a[4]={1,2,3,4};
   int *ptr1=(int *)(&a+1);//指向a数组后面的内存单元,&a+1表示向后移16个存储单元
   int *ptr2=(int *)((int)a+1);//表示a的存储单元的地址增加一个字节
   printf("%x,%x",ptr1[-1],*ptr2);//ptr1[-1]其实指向的是a数组的最后一个单元,*ptr1则表示a数组的地址后移一个字节之后的4个连续存储单元所存储的值
   return 0;
}
这是我讲课时一个学生问我的题,他在网上看到的,据说难倒了n 个人。我看题之后告诉他,这些人肯定不懂汇编,一个懂汇编的人,这种题实在是小case。下面就来分析分析这个问题:

根据上面的讲解,&a+1 与a+1 的区别已经清楚。

ptr1:将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr,ptr1 肯定指到数组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0x4。
ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int*类型的值赋给ptr2,也就是说*ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。

其内存布局如下图:

好,问题就来了,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用下面这个函数来测试当前系统的模式。
int checkSystem( )
{
   union check
   {
      int i;
     char ch;
   } c;
   c.i = 1;
   return (c.ch ==1);
}
如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。 这边的内容还不太懂???

下面讨论在Intel机子上windows系统下的c内存分配

如图演示会更容易懂些。

1,数组元素在内存中的顺序

也请注意程序顺序与内存顺序的关系

       int b=10;

       int a[3]={1,2,3};  

       int c=11;

a的地址跟a[0]的地址一样,但是

指针的加减运算 以所指变量的大小为单位:

int(a+1)-int(a)=4;

int(&a+1)-int(a)=4*3=12;    

这里a代表数组首元素地址,所以a+1是元素的移动。&a代表数组的首地址,&a+1,是移动整个数组的长度。

贴一个示例看一下

int a[2][3] ={1,2,3,4,5,6};
	cout<<*(*a + 1)<<endl;
	cout<<**(a +1)<<endl;
	cout<<**(&a + 1)<<endl;

	int b[2] = {3,4};
	cout<<*(b +1)<<endl;
	cout<<*(&b + 1)<<endl;

输出: ,我认为这里的两个地址已经是数组外的地址了。也就是这个取值越界了。但数组指针+1时是如何取得还要再好好看看。????

const的用法???函数指针与指针函数??

1.

定一个int类型,长度为3的数组:
int a[3] = {1, 2, 3};
再定义一个指向长度为3 的数组的指针:
int (*p)[3];
将p指向数组a:
p = &a; 
cout<<(*p)[0]<<(*p)[1]<<(*p)[2];
输出123
char  (*j)[4];
char cc[3][4] = {'a', 'b','c','d', 'e','f','g', 'h','i','j', 'k','l'};	
j = cc;
cout<<(*j)[0]<<(*j)[1]<<(*j)[2];
输出abc

char  (*j)[4];
	char cc[3][4] = {'a', 'b','c','d', 'e','f','g', 'h','i','j', 'k','l'};	
	j = cc;
	cout<<(*j)[0]<<(*j)[1]<<(*j)[2]<<(*(j+1))[3]<<(*(j+2))[3];;

输出abchl

组指针与指针数组    

数组指针是指向数组地址的指针,其本质为指针; char (*k)[9];
指针数组是数组元素为指针的数组(例如 int *p[3],定义了p[0],p[1],p[2]三个指针),其本质为数组。
数组指针的使用在某些情况下与指针数组很相似,要注意区别。
二维数组指针
为了能更好地理解数组指针,与普通指针及二级指针的区别,下面举例说明一下。
例如:{int a[4][5];int (*p)[5]=a;}这里a是个二维数组的数组名,相当于一个二级指针常量;
p是一个指针变量,它指向包含5个int元素的一维数组, 此时p的增量 以它所指向的 一维数组长度为单位;
*p+i是二维数组a[0][i]的地址;
*(p+2)+3表示a[2][3]地址(第一行为0行,第一列为0列),*(*(p+2)+3)表示a[2][3]的值。
//(*p)[5]其中的5换成其他的数字在vc++6.0环境中都无法通过编译
(*p)[5]其中的5在上述例子中没有表示任何意思你可以换成除0以外的整数,[5]的作用就是帮助你记忆说你所指向的一维数组的长度。(不过除了与定义的二维数组的长度一致的不会警告之外 )其他的数会警告但是不影响结果。

2.  函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。函数指针有两个用途:调用函数和做函数的参数

函数指针的声明方法为:
函数类型 (标志符 指针变量名) ( 形参列表);
注1:“函数类型”说明函数的返回类型,“(标志符 指针变量名 )”中的括号不能省,若省略整体则成为一个函数说明,说明了一个返回的 数据类型是指针的函数,后面的“ 形参列表”表示指针变量指向的函数所带的 参数列表。例如:
int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
赋值时函数func不带括号,也不带 参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。

3. typedef 的用法

typedef  int  inta;


 

五。程序中关于堆栈大小的划定

http://blog.csdn.net/liuhuiyi/article/details/8207021


用内核调试器kadb来反汇编内核二进制文件的映像,将结果输出到一个ASCALL文件中。

然后用grep对这个文件进行搜索,寻找操作数指示偏移量为19的“store“指令。

字节对齐(仅变量)

http://www.cnblogs.com/repository/archive/2011/01/13/1933721.html

c、c++在定义变量,数组时的内存布局及内存字节对齐

http://blog.csdn.net/onezeros/article/details/5687129

VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。 n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 
否则必须为n的倍数。下面举例说明其用法。 
#pragma pack(push) //保存对齐状态 
#pragma pack(4)//设定为4字节对齐 
struct test 

char m1; 
double m4; 
int m3; 
}; 
#pragma pack(pop)//恢复对齐状态 
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到 结构的大小为24。 (请读者自己分析)
以上详见 http://hi.baidu.com/asmsky/item/a8b94d4a918acb0b6cc2f076

 Win32平台下的微软 编译器(cl.exe for 80×86)的对齐策略: 
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 
备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。 
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 
备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 
备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

http://www.jb51.net/article/45406.htm  数组嵌套对齐规则

对齐长度长于struct中的类型长度最长的值时,设置的对齐长度等于无用.
数组对齐的长度是按照数组成员类型长度来比对的.
嵌套的结构体中,所包含的结构体的对齐长度是结构体的对齐长度.
详细出处参考:http://www.jb51.net/article/45406.htm

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

32位系统和64位系统变量所占空间总结 .

随着硬件的不断降价,我们的计算机内存也越来越大。而我们经常使用的Windows操作系统大都是32位的,4G内存成为一个瓶颈问题。作为软件开发爱好者,如何在64位系统下做程序设计成为了日程中的话题。下面我根据程序在不同平台下的运行情况,总结了一下C/C++常用数据类型所占的内存空间。

Win64, 也就是X64编译配置下:

 

char:1字节;

short:2字节;

int:4字节;

long:4字节;

long long:8字节;

float:4字节;

double:8字节;

long double:8字节;

wchar_t:2字节;

bool:1字节;

 

char*:8字节;

bool*:8字节;

short*:8字节;

int*:8字节;

long*:8字节;

long long*:8字节;

float*:8字节;

double*:8字节;

long double*:8字节;

wchar_t*:8字节;

下面是Win32, 也就是X86编译配置下变量所占字节数:

 

char:1字节;

short:2字节;

int:4字节;

long:4字节;

longlong:8字节;

float:4字节;

double:8字节;

long double:8字节;

wchar_t:2字节;

bool:1字节;

 

char*:4字节;

bool*:4字节;

short*:4字节;

int*:4字节;

long*:4字节;

long long*:4字节;

float*:4字节;

double*:4字节;

long double*:4字节;

wchar_t*:4字节;

     总结:经上述比较,一般变量在X86和X64系统下长度没什么区别,区别在于指针的寻址范围从32位增加到了64位。如果考虑对64位系统下程序的兼容性,指针是唯一需要注意的地方。从32位系统过渡到64位系统不同于从16位系统过渡到32位系统,因为DOS系统下int只有2个字节,而Windows系统下int是4个字节,过渡到64位系统后int的字节数没有增加。如果使用 Windows系统编程,支持64位需要Visual Studio 2005/2008均可,而早期版本是不支持64位平台的。

 文章出处:http://hi.baidu.com/syjfd/item/fd169f0f41369322a1312de9

补充:c基础知识 _tchar和char的区别

因为C++支持两种字符串,即常规的ANSI编码(使用""包裹)和Unicode编码(使用L""包裹),这样对应的就有了两套字符串字符串处理函数,比如:strlen和wstrlen,分别用于处理两种字符串   微软将这两套字符集及其操作进行了统一,通过条件编译(通过_UNICODE和UNICODE宏)控制实际使用的字符集,这样就有了_T("")这样的字符串,对应的就有了_tcslen这样的函数   为了存储这样的通用字符,就有了TCHAR:   当没有定义_UNICODE宏时,TCHAR = char,_tcslen = strlen   当定义了_UNICODE宏时,TCHAR = wchar_t , _tcslen = wstrlen   当我们定义了UNICODE宏,就相当于告诉了编译器:我准备采用UNICODE版本。这个时候,TCHAR就会摇身一变,变成了wchar_t。而未定义UNICODE宏时,TCHAR摇身一变,变成了unsigned char 。这样就可以很好的切换宽窄字符集。   tchar可用于双字节字符串,使程序可以用于中日韩等国 语言文字处理、显示。使编程方法简化。
_TCHAR 是一种通用字符类型,它有可能是char型,也有可能是wchar_t型,决定它是那一种的关键是你的程序是否定义了#define _UNICODE ,当你没有定义时(ASII),_TCHAR就是char,定义了之后就是wchar_t型。_TCHAR有这种特性是因为微软制定的这种字符映射规则。_T()就是做了这种转换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值