??xml version="1.0" encoding="utf-8" standalone="yes"?> 与在前面:++(--)有太多让人困惑的地方,(i++)+(i++)?++i)+(++i)有什么不?Z么不?如果从机器的角度ȝ解,׃豁然开朗?/p>
先来看段E序: (1)在VC 6.0下: 对于(i++)+(i++): l果Qi=5,j=6相应的汇~代码ؓQ?/p>
(3)如果q段代码用java实现Q结果会怎样呢? E序: 对于(++i)+(++i): 对于(i++)+(i++): Java与VC/gccZ么会有如此的区别呢?其实原因很简单,VC/gcc生成的是本地代码Q而X86处理器是Z寄存器的架构Q也是如果它要q行了两个数相加Q它会先把两个数Ud寄存器,再进行加法运。而Java虚拟机是一U基于栈的架构,如果它要q行两个数相加,它会先弹Z个数Q再q行加法q算Q再结果入栈?/p> 一l数l的名字大部分情况下可以当成一个常指针来看Q也是_ 则数l名a可以在大部分p能用的地方用,有两U情冉|较特D: 在内存排布上Q一l数l和一阶指针指向的内存是完全一L?/p> 对于高维数组Q以二维ZQ其他完全一PQ情况和一l数l完全不一栗例如对于数l和指针Q?/p> 他们没有Q何可比性。初学者从一l数l中的知识简单的推断出a是一个等价于int**的东西,*a可以得C?int*的|q其实是完全错误的?/p> 从内存排布上Q数l按照先低维后高l的序一ơ排列数l的每个元素。其中低l到高维是指定义数组ӞLl名近的维Zl_反之为高l_例如上面q个数组Q?是低维的维敎ͼ4为高l的l数Q因此,在内存中Q上面这个数l占?2个int单元Q所有单元靠在一P光序则?/p> 如果a是一个和二阶指针{h的常量,那么他的值应该指向一个指针数l才对,而实际上q样的指针数l是不存在?/p> 从前面这个排布还可以看出一个问题,在排列过E中Q数l的最低维不确定是没有关系的,但是Z安排好数l元素的先后序Q高l的l数必须定Q也是说在使用数组cd来定义指针时Q必首先确定高l_例如Q?/p> 是可以的Q但?/p> ׃行?/p> 仔细考虑一l数l和多维数组Q是不是~译器对一l数l特D处理了Q其实也不是Q数l名其实代表的是数组对象也就是其W一个元素的地址Q从q个角度Ԍ一l数l名a代表了a[0]的地址Q二l数l名a代表了a[0][0]的地址Q高l数l的名字从这个意义讲更接q?阶指? 今天写程序的时候要用到二维数组作参Cl一个函敎ͼ我发现将二维数组作参数进行传递还不是惌得那么简单,但是最后我也解决了遇到的问题,所以这文章主要介l如何处理二l数l当作参C递的情况Q希望大家不至于再在q上面浪Ҏ间?/span> 下文是我从互联网上download的一文章,讲的很好Q但是我后面指出问题所在,q加以改q,希望对你有所帮助Q?/span> 首先Q我引用了K强先生~著的?span style="line-height: 34px; ">C [原文开?span style="line-height: 34px; ">] 可以用二l数l名作ؓ实参或者Ş参,在被调用函数中对形参数组定义时可以指定所有维数的大小Q?span style="line-height: 34px; ">也可以省略第一l的大小说明Q如Q?/span> void Func(int array[3][10]); void Func(int array[][10]); 二者都是合法而且{hQ?span style="line-height: 34px; ">但是不能把第二维或者更高维的大省?/span>Q如下面的定义是不合法的Q?/span> void Func(int array[][]); 因ؓ从实参传递来的是数组的v始地址Q在内存中按数组排列规则存放(按行存放)Q而ƈ不区分行和列Q如果在形参中不说明列数Q则pȝ无法军_应ؓ多少行多列Q?span style="line-height: 34px; ">不能只指定一l而不指定W二l_下面写法是错误的Q?/span> void Func(int array[3][]);实参数组l数可以大于形参数组Q例如实参数l定义ؓQ?/span> void Func(int array[3][10]); 而Ş参数l定义ؓQ?/span> int array[5][10]; q时形参数组只取实参数组的一部分Q其余部分不起作用?/span> [原文l束] 大家可以看到Q将二维数组当作参数的时候,必须指明所有维数大或者省略第一l的Q但是不能省略第二维或者更高维的大,q是q译器原理限制的。大家在学编译原理这么课E的时候知道编译器是这样处理数l的Q?/span> 对于数组 int p[m][n]; 如果要取p[i][j]的?span style="line-height: 34px; ">(i>=0 && i<m && 0<=j && j < n)Q编译器是这样寻址的,它的地址为: p + i*n + j; 从以上可以看出,如果我们省略了第二维或者更高维的大,~译器将不知道如何正的d。但是我们在~写E序的时候却需要用到各个维数都不固定的二维数组作ؓ参数Q这难办了Q编译器不能识别阿,怎么办呢Q不要着急,~译器虽然不能识别,但是我们完全可以不把它当作一个二l数l,而是把它当作一个普通的指针Q再另外加上两个参数指明各个l数Q然后我们ؓ二维数组手工dQ这样就辑ֈ了将二维数组作ؓ函数的参C递的目的Q根据这个思想Q我们可以把l数固定的参数变为维数随即的参数Q例如: void Func(int array[3][10]); void Func(int array[][10]); 变ؓQ?/span> void Func(int **array, int m, int n); 在{变后的函CQ?span style="line-height: 34px; ">array[i][j]q样的式子是不对?span style="line-height: 34px; ">(不信Q大家可以试一?span style="line-height: 34px; ">)Q因为编译器不能正确的ؓ它寻址Q所以我们需要模仿编译器的行为把array[i][j]q样的式子手工{变ؓQ?/span> *((int*)array + n*i + j); 在调用这L函数的时候,需要注意一下,如下面的例子Q?/span> int a[3][3] = { {1, 1, 1}, {2, 2, 2}, {3, 3, 3} }; Func(a, 3, 3); Ҏ不同~译器不同的讄Q可能出?span style="line-height: 34px; ">warning 或?span style="line-height: 34px; ">error,可以q行强制转换如下调用Q?span style="line-height: 34px; "> Func((int**)a, 3, 3); 其实多维数组和二l数l原理是一LQ大家可以自己扩充的多维数组Q这里不再赘q。写到这里,我先向看了这文章后悔的人道歉,费你的旉了。下面是一个完整的例子E序Q这个例子程序的主要功能是求一个图中某个顶点到其他点的最短\l,图是以邻接矩늚形式存放?span style="line-height: 34px; ">(也就是一个二l数l?span style="line-height: 34px; ">)Q其实这个函C是挺有用的,但是我们q篇文章的重点在于将二维数组作ؓ函数的参C递?/span> 上文l束Q上文最后指Z实现二位数组作ؓ函数参数的方法,但是它实现的是将静态的二位数组作ؓ参数Q但是如何将动态而ؓ数组作ؓ参数呢?上面的方法显然是不合适的Q下面是我琢出来的Ҏ?/span> 先将静态数l作为参数的代码贴出? #include <iostream> 下面是动态二位数l作为函数参数时的代码: #include <iostream> q样Q以上的两段代码不仅实现了堆和栈之间数据的传递,而且实现了堆和堆之间数据的传递!
]]>
{
int i=3;
int j=(i++)+(i++);
// int j=(++i)+(++i);
printf("%d,%d\n",i,j);
}
对于(i++)+(i++):
l果Qi=5,j=6
相应的汇~代码ؓ(有详l注?Q?/p>
03 45 FC add eax,dword ptr [ebp-4] ;i+i=6
89 45 F8 mov dword ptr [ebp-8],eax ;6->j
8B 4D FC mov ecx,dword ptr [ebp-4] ;i->ecx(=3)
83 C1 01 add ecx,1 ;ecx=4
89 4D FC mov dword ptr [ebp-4],ecx ;4->i
8B 55 FC mov edx,dword ptr [ebp-4] ;i->edx
83 C2 01 add edx,1 ;edx=5
89 55 FC mov dword ptr [ebp-4],edx ;5->i
对于(++i)+(++i)Q?br />l果Qi=5,j=10
相应的汇~代码ؓQ?/p>
83 C0 01 add eax,1 ;eax=4
89 45 FC mov dword ptr [ebp-4],eax ;4->i
8B 4D FC mov ecx,dword ptr [ebp-4] ;i->ecx
83 C1 01 add ecx,1 ;ecx=5
89 4D FC mov dword ptr [ebp-4],ecx ;5->i
8B 55 FC mov edx,dword ptr [ebp-4] ;i->edx
03 55 FC add edx,dword ptr [ebp-4] ;edx=10 ,即i+i
89 55 F8 mov dword ptr [ebp-8],edx ;10->j
(2)在gcc 3.2.2?
8b 55 fc movl -4(%ebp), %edx ;i->edx (=3)
8b 45 fc movl -4(%ebp), %eax ;i->eax (=3)
8d 04 10 leal (%eax,%edx), %eax ;i+i=6 ->eax
89 45 f8 movl %eax, -8(%ebp) ;6->j
8d 45 fc leal -4(%ebp), %eax ;&i->eax
ff 00 incl (%eax) ;i++ ,即i=4,注意q里为寄存器间接d
8d 45 fc leal -4(%ebp), %eax ;&i->eax
ff 00 incl (%eax) ;i++,即i=5
对于(++i)+(++i)Q?br />l果Qi=5,j=10
相应的汇~代码ؓQ?/p>
leal -4(%ebp), %eax ;&i->eax
incl (%eax) ;i++,即i=4
leal -4(%ebp), %eax ;&i->eax
incl (%eax) ;i++, i=5
movl -4(%ebp), %eax ;i->eax, eax=5
addl -4(%ebp), %eax ;i+i ->eax ,eax=10
movl %eax, -8(%ebp) ;10->j
可见Q对于VC6.0和gccQ二者的l果一_但是gcc 3.2.2生成的汇~代码明显比VC6.0高效、简z。这也许是因为VC 6.0出现较早的原因吧?/p>
public static void main(String[] args) {
int i=3;
int j=(i++)+(i++); //5,7
//int j=(++i)+(++i); //5,9
System.out.println(i+","+j);
}
}
i=5,j=9。结果点意外!
来看看它的字节码?
//5,9
0: iconst_3 ;帔R3入栈
1: istore_1 ;从栈中弹?,存入i,i=3
2: iinc 1, 1 ;i++, i=4
5: iload_1 ;i压入??入栈
6: iinc 1, 1 ; i++,i=5
9: iload_1 ;i入栈,?入栈
10: iadd ;从栈中弹Z个intcd的数相加,l果入栈,?入栈
11: istore_2 ;从栈中弹?,存入j,即j=9
i=5,j=7。结果也很意?
也来看看它的字节码吧:
//5,7
0: iconst_3 ;帔R3入栈
1: istore_1 ;从栈中弹?,存入i,i=3
2: iload_1 ;i入栈,?入栈
3: iinc 1, 1 ;i++,即i=4
6: iload_1 ;i入栈,?入栈
7: iinc 1, 1 ;i++,即i=5;注意Q?没有入栈,所以此时栈中的Cؓ3?
10: iadd ;从栈弹出两个intcd数相加,l果入栈,?入栈
11: istore_2 ;从栈中弹?Q存入j,即j=7
一l数l和一U指针的相同和差?/h2>
int a[3] int * const p;
高维数组和高阶指?/h2>
int a[3][4] int**p;
a[0][0]Qa[0][1]Qa[0][2]Qa[0][3] a[1][0]Qa[1][1]Qa[1][2]Qa[1][3] a[2][0]Qa[2][1]Qa[2][2]Qa[2][3]
int (*p)[3] = new int[4][3]
int (*p)[] = new int[4][3]
回到一l数l?/h2>
]]>
using namespace std;
void Calc(int **A, int m, int n);
int _tmain(int argc, _TCHAR* argv[])
{
int row = 0;
int col = 0;
int i = 0;
int A[3][3];
for (row = 0; row < 3; row++)
{
for (col = 0; col < 3; col++)
{
A[row][col] = row + col;
}
}
Calc((int **)A, 3, 3);
return 0;
}
void Calc(int **A, int m, int n)
{
if (NULL == A || m <1 || n < 1)
{
return;
}
int row = 0;
int col = 0;
int i = 0;
int j = 0;
int **matrix = NULL;
matrix = new int*[m];
if (NULL == matrix)
{
return;
}
for (row = 0; row < m; row++)
{
matrix[row] = new int[n];
if (NULL == matrix[row])
{
for (i = 0; i < row; i++)
{
delete []matrix[i];
matrix[i] = NULL;
}
delete []matrix;
matrix = NULL;
return;
}
}
for (row = 0; row < m; row++)
{
for (col = 0; col < n; col++)
{
matrix[row][col] = *((int *)A + row * n + col);
//matrix[row][col] = A[row][col];
}
}
for (row = 0; row < m; row++)
{
for (col = 0; col < n; col++)
{
cout<<matrix[row][col]<<" ";
}
cout<<"\n";
}
}
using namespace std;
void Calc(int **A, int m, int n);
int main(int argc, char* argv[])
{
int row = 0;
int col = 0;
int i = 0;
int **A = NULL;
A = new int*[3];
if (NULL == A)
{
return 0;
}
for (row = 0; row < 3; row++)
{
A[row] = new int[3];
if (NULL == A[row])
{
for (i = 0; i < row; i++)
{
delete []A[i];
A[i] = NULL;
}
delete []A;
A = NULL;
return 0;
}
}
for (row = 0; row < 3; row++)
{
for (col = 0; col < 3; col++)
{
A[row][col] = row + col;
}
}
Calc((int **)A, 3, 3);
return 0;
}
void Calc(int **A, int m, int n)
{
if (NULL == A || m <1 || n < 1)
{
return;
}
int row = 0;
int col = 0;
int i = 0;
int j = 0;
int **matrix = NULL;
matrix = new int*[m];
if (NULL == matrix)
{
return;
}
for (row = 0; row < m; row++)
{
matrix[row] = new int[n];
if (NULL == matrix[row])
{
for (i = 0; i < row; i++)
{
delete []matrix[i];
matrix[i] = NULL;
}
delete []matrix;
matrix = NULL;
return;
}
}
for (row = 0; row < m; row++)
{
for (col = 0; col < n; col++)
{
//matrix[row][col] = *((int *)A + row * n + col);
matrix[row][col] = A[row][col];
}
}
for (row = 0; row < m; row++)
{
for (col = 0; col < n; col++)
{
cout<<matrix[row][col]<<" ";
}
cout<<"\n";
}
}
注意上面的代码的不同之处Q即动态二l数l作为函数参数时Q在函数里面应用时候要其伪装成静态二l数l!
]]>
6.2l构与函?br />p114---(1)不同的结构体变量之间q行赋gq行单的拷贝g递?br />p115---(2)l构q算W?." ?->",用于函数调用?()"以及用于下标?[]"的算术符优先U最高?br />
6.3l构数组
p116---(1)l构体数l初始化
struct key {
char *word;
int count;
}keytab[] = {
{"auto",0},
{"break",0},
{"case",0},
{"while",0}
};
p118---(2)计算l构体数l中的结构体个数Ӟ需要用到sizeof, sizeof keytab / sizeof(struct key) 。另外条件编译语?if中不能用sizeofQ因为预处理器部队类型名q行分析.但预处理器ƈ不计?define语句中的表达?因此?define中用sizeof是合法的?br />
6.4指向l构的指?br />p120---(1)两个指针之间的加法运是非法的。但?指针的剑法运却是合法的,mid=(low+high)/2是错误的,但是mid=low+(high-low)/2可以了?br />p115---(2)千万不要以ؓl构的长度等于各成员长度的和。因Z同的对象有不同的寚w要求Q所以,l构中可能会出现未命名的“I穴”。一般需要用sizeof来正获得结构体长度?br />
6.5自引用结?br />
6.6表查?br />
6.7cd定义(typedif)
p127---(1)typedef中声明的cd在变量位|出玎ͼ而不是紧接在关键字typedef之后。typedef在语法上cM与extern{?br />p128---(2)typedefcM?define语句Q但׃typedef是由~译器解释的Q因此它的问题替换功能要过预处理器的能力。例? typedef int (*PFI)(char *,char*);
6.8联合
p128---(1)联合提供了一U方式,以在单块存储Z理不同cd的数据,目的在于一个变量可以合法的保存多种数据cd中的M一U类型?br />p129---(2)联合体变量读取的cd必须是最q一ơ存入的cd。程序员赋Dt当前保存在联合中的cdQ如果保存的cd和读取的cd不一_其结果取决于具体的实现?br />p130---(3)联合是一个结构,他得所有成员相对于基地址的偏U量都是0Q此l构I间要大到够容Ux“?#8221;的成员?br />
6.9位字D?br />p131---(1)struct {
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
}flag;
q里定义了一个变量flagQ它包含3?位的字段。冒号后的数字表C字D늚宽带(2q制)?br />flag.is_keyword = 1;
]]>
p79---(1)指针是能够存放一个地址的一l存储单元,通常是两个或者四个字节?br />p80---(2)int *ip Q该声明语句表明*ip的结果是一个intcd?br />
5.2指针与函数参?br />
5.3指针与数l?br />p84---(1)如果pa指向数组中的某个特定元素Q那么根据指针运的定义Qpa+1指向下一个元素,pa+i指向pa所指向数组元素之后的第i个元素。例?(pa+i){同于a[i]?br />p85---(2)我们必须CQ数l名和指针之间有一个不同之处。指针是一个变量,因此Q在c语言中,语句pa=a和pa++都是合法的。但数组名不是变量,因此Q类ga=paQa++的语句都是非法的?br />p86---(3)如果信相应的元素存在,也可以通过下标讉K数组W一个元素之前的元素。类|p[-1]、p[-2]q样的表辑ּ在语法上也是合法的,它们分别引用位于p[0]之前的两个元素,当然引用数组边界之外的对象是非法的?br />
5.4地址术q算
p87---(1)c语言保证Q?永远不是有效的数据地址Q因此返回?可用来表C发生了异常事g?br />p88---(2)指针与整C间不能相互{换,?是惟一的例外:帔R0可以赋值给指针Q指针也可以和常?q行比较。程序中l常用符号常量NULL代替帔R0Q这样便于更清晰地说明常?是指针的一个特D倹{符号常量NULL定义在标准头文g<stddef.h>中?br />p89---(3)有效的指针运包括相同类型指针之间的赋D;指针同整C间的加法或减法运;指向相同数组中元素的两个指针间的减法或比较运,指针赋gؓ0或指针与0之间的比较运。其他所有Ş式的指针q算都是非法的,例如两个指针间的加法Q乘法,除法Q移位或屏蔽q算Q指针同float或doublecd之间的加法运;不经强制cd转换而直接将指向一U类型对象的指针赋值给指向另一U类型对象的指针q算?br />
5.5字符指针与函?br />p90---(1)注意char amessage[] = "now"; 和char *pmessage = "now";的区别,amesaage是一个仅仅存攑ֈ始化字符串以及空字符'\0'的一l数l。它始终指向同一个存储位|。而pmeesage是一个指针,其初值指向一个字W串帔RQ之后还可以被修改以指向其他地址?br />
5.6指针数组以及指向指针的指?br />p94---(1)char *lineptr[MAXLINES]是一个指针数l,lineptr[i]是一个字W指针,?lineptr[i]是该指针指向的第i个文本行的首字符?br />
5.7多维数组
p95---(1)char daytab[2][3] = {{1,2,3},{1,2,3}};
p96---(2)如果二l数l作为参C递给函数Q那么在函数的参数声明中必须指明数组的列数?/span>
p97---(3)特别注意Q方括号[]的优先高于*的优先Q所以int (*day)[13] ?int *day[13]是完全不同的含义。前者是表示一个二l数l,每个元素放intcdQ后者表CZl指针数l,里面攄是指向一个intcd的地址?/span>
5.8指针数组的初始化
5.9指针与多l数l?/font>
p98---(1)到目前ؓ止,指针数组最频繁的用处是存放h不同长度的字W串
5.10命o行参?/font>
p98---(1)调用d?/span>mainӞ它有两个参数。第一个参?/span>argc的DC行程序时命o行中参数的数目;W二个参?/span>argv是一个指向字W串数组的指针,其中每一个字W串对应一个参数?/span>
p99---(2)按照c语言的约定,argv[0]的值是启动E序的程序名Q因?/font>argc的Dؓ1.如果argc的gؓ1Q则说明E序后面没有命o行参数。第一个可选的参数?/font>argv[1],而最后一个可选参Cؓargv[argc-1]。另外,ANSI标准要求argv[argc]必须Z个空指针?/font>
p101---(3)注意(*++argv)[0]?/font>*++argv[0]的区别,前者是行移动,后者是列移动?/font>
5.11指向函数的指?/font>
p104---(1)注意int (*comp)(void *,void *)?/font>int *comp(void *,void *)的区别,前?/font>comp是一个函数指针指向一个返回类型ؓint的函敎ͼ而后?/font>comp为函数名q回cd?/font>int指针?/font>
5.12复杂声明Q这节后面的E序有点复杂Q直接无视,哈哈~~Q?nbsp;
p106---(1)char (*(*x())[])() ,x是一个无参函敎ͼq回一个指针,指针指向一个数l,数组中的元素也是指针Q指向一个返回类型ؓchar的无参函数?/font>
p106---(2)char (*(*x[3])())[5] ,x是一个长度ؓ3的数l,数组中的元素是指针,指向一个无参的函数Q该函数q回cd也是指针Q指向一个长度ؓ5?/font>charcd数组?/font>
4.9初始?br />p72---(1)在不昑ּ初始化的情况下,外部变量和静态变量都被隐式初始化ؓ0Q而自动变量和寄存器变量的初值则没有意义?br />p72---(2)外部变量与静态变量来_初始化的表达式必L帔R表达?即不能是之前已经定义q的??br />
4.10递归
4.11C预处理器
4.11.1文g包含
p75---(1)#include的行被替换为由文g名指定的文g的内宏V如果文件名用引号引hQ则在源文g所在位|查找该文gQ如果没有找到文Ӟ或者用尖括号把文件名引v来,则将Ҏ相应的规?查找该文Ӟq个规则同具体的实现有关?br />
4.11.2宏替?br />p76---(1)通常情况下,#define指定占一行,替换文本?define指定行尾部的所有剩余部分内容,但也可以把一个较长的宏定义分成若q行Q这旉要在待箋的行末尾加上一个反斜杠W\?br />p76---(2)宏定义也可以带参敎ͼ如define max(A,B) ((A)>(B)?(A):(B)),调用时候只需要x = max(i,j)可以。但仔细考虑一下max的展开式,׃发现存在一些缺P其中表达式要重复计算2ơ,那么假如q样调用x = max(i++,j++)l果会i或j自增两次?br />p77---(3)预处理器q算W?#提供了一U连接实际参数的手段。如果替换文本中的参C##盔RQ则该参数将被实际参数替换,##与前后的I白W将被删除,q对替换后的l果重新扫描。例?#define paster(f,b) f##b Q调用的时候paster(i,5)l果是i5的参数?br />
4.11.3条g包含
p78---(1)#if语句对其中的帔R整Ş表达式(不能包含sizeof、类型{换、enum帔RQ进行求|若表辑ּ的值非0则包含其后各行,直到遇到#endif\#elif?else语句为止?br />q有其他?ifdef?ifndef?endif{等。。。?/p>