c语言指针疑惑[转载]
c99的动态数组是在栈上面开辟的,而new出来的是在堆上面开辟的。
栈和堆的地址是从两端相向增长的。
栈很小,一般只有几十k,vc6好像是64k。
堆很大,在win32的虚拟地址空间可以分配到2g的内存。
栈由程序自动操作,包括局部变量,参数传递,函数跳转时的返回地址等信息。
堆由程序员自行管理内存,优势,节省空间,不用时即释放,缺点,可能因使用不但造成内存泄露,野指针,造成程序错误。
1、把数组名赋值给指针表示将数组的首元素的地址赋予此指针。
int a[3] = {2,5,8};
int* p = a;
2、a[0]是a数组的首元素,而&则是取地址运算符,所以“ &a[0]”取得的同样是a数组的首元素的地址,因此这段代码的含义和代码段是一致的。
int a[3] = {2,5,8};
int* p = &a[0];
3、这两句不都是声明一个字符串吗?有什么区别吗?
char * c1 = "Hello";
char c2[6] = "World";
"Hello"是一个字符串,然后让char指针c1指向这个字符串的首元素的地址。
第二句是声明一个长度为6的char类型数组,并且设置数组的初始值为"World"。注意末尾还有一个隐藏的“\0”,所以长度是6,而不是5。
4、数组指针的加减运算
对于指向数组的指针变量可以进行加减运算,比如对于指向数组的指针p,p++、p--、p+2等都是合法的。这里的加减运算并不是针对数组元素的,而是针对指针位置的,比如p当前指在首元素上,那么p++后就指在第二个元素上;比如p当前指在第5个元素上,那么p=p-2后,p就指在第3个元素上。
例子:
char * c1 = "Hello";
printf("%c\n",*c1);//输出H
c1++;
printf("%c\n",*c1);//输出e
5、指针之间的运算
两个指针之间可以进行减法运算,只有指向同一个数组的两个指针之间进行减法运算才有意义,而指向不同数组的两个指针之间进行减法运算则没有意义。为什么呢?
指针其实就是内存中的地址,两个指针的减法运算计算的就是两个内存地址之间的元素的个数,比如整数指针p1指向内存地址2008H,整数指针p2内存地址2000H,而整数占4个字节,所以p1-p2的结果就是(2008H-2000H)/4=2,也就是 p1和p2 之间相差 2 个元素。很显然两个指针进行加法运算或者两个指向不同变量的指针进行减法运算都是没有意义的。
例子:
int a1[5]={3,5,6,8,2};
int* p1 = a1;//p1是指向第0个元素的地址
int* p2 = &a1[3];//p2指向的是第3个元素的地址
p1++;//p1指向了第1个元素
printf("%d",p2-p1);//输出2
6、指针之间的大小比较
指向同一个数组的两个指针之间进行大小的比较是有意义的。比较结果为地址高低的比较:
例子:
int a1[5]={3,5,6,8,2};
int* p1 = a1;
int* p2 = &a1[3];
p1++;
printf("%d\n",p2<p1);//输出0,因为p2的地址比p1高2位
p1=p1+2;//p1在数组内向高位移动两位
printf("%d\n",p2==p1);//输出1 ,因为p1和p2的位相等
7、数组做为参数传递给函数
可以将数组做为传递给函数,比如下面的代码就是将传入输入的每个元素乘以2:
void makeDoule(int arr[],int len)
{
int i=0;
for(i=0;i<len;i++)
{
arr= arr*2;
}
}
int main(int argc, char *argv[])
{
int a1[5]={3,5,6,8,2};
int i = 0;
int len=sizeof(a1)/sizeof(int);
makeDoule(a1,len);
for(i=0;i<len;i++)
{
printf("%d ",a1);
}
}
运行结果:
6 10 12 16 4传递给makeDoule函数的是a1,我们知道,一个数组的名字其实就是指向数组首元素的地址。所以makeDoule函数得到的arr就是数组a1的指针,那么在函数内部对arr进行操作的话就会改变a1数组。
所以makeDoule函数也可以写成下面的样子,这两个函数是等价的:
void makeDoule(int* arr,int len)
{
int i=0;
for(i=0;i<len;i++)
{
arr= arr*2;
}
}
当然写成下面的形式更符合指针的使用习惯,因此推荐这种用法:
void makeDoule(int* arr)
{
int i=0;
for(i=0;i<5;i++)
{
//arr+i指向的值设置为arr+i指向的值的二倍。
*(arr+i)= *(arr+i)*2;
}
}
有的同学可能会问,为什么makeDoule函数还需要传递数组的长度len呢?makeDoule函数这样写不就行了吗?
void makeDoule(int* arr)
{
int i=0;
for(i=0;i<5;i++)
{
arr= arr*2;
}
}
这样写在这个函数中是没有问题的。但是这个makeDoule函数不一定为只为a1服务,函数的最重要的特征是可以复用,也就是可以被其他地方调用,如果我们想用makeDoule函数为另外一个长度为20的数组进行“每个元素乘以2”的操作,那么将数组长度5写死在函数中就会有问题了。
那么为什么不在函数内部计算数组的长度呢?省得还传递一个len参数
void makeDoule(int arr[])
{
int i=0;
int len = sizeof(arr)/sizeof(int);
for(i=0;i<len;i++)
{
arr= arr*2;
}
}
int main(int argc, char *argv[])
{
int a1[5]={3,5,6,8,2};
int i = 0;
int len=sizeof(a1)/sizeof(int);
makeDoule(a1);
for(i=0;i<len;i++)
{
printf("%d ",a1);
}
}
运行以后程序竟然输出了:6 5 6 8 2
只有第一个元素被“乘以2”。为什么呢?
运行下面的程序试一试:
void test(int arr[])
{
printf("%d\n",sizeof(arr)/sizeof(int));
}
int main(int argc, char *argv[])
{
int a1[5]={3,5,6,8,2};
printf("%d\n",sizeof(a1)/sizeof(int));
test(a1);
}
运行结果竟然是:
5
1为什么在main函数中计算数组的大小是5,在test函数中计算数组arr的大小就变成了1了呢?在C语言中可以通过sizeof的方式取得一个数组的尺寸,这是我们已经知道的。但是一旦把这个数组传递给函数的时候,到了函数内部使用的就是指向这个数组的指针了,虽然在test函数中arr声明的是数组,但是这里的arr只是一个指针而已了,arr本质上只是一个int类型的指针,而int类型的指针的大小是4,所以sizeof(arr)/sizeof(int)的结果就是1。这点是经常容易犯错的,需要特别注意。如果对指针还有什么不清楚的,可以到这个网址查询指针的学习资料:http://www.jsj321.com/forum/forumdisplay.php?fid=4 ,指针是比较不好理解的,因此学习过程中要多试验,多思考,不要气馁。8、多维数组的指针
设有一个二维数组int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}
(1)C语言允许把一个二维数组分解为多个一维数组来处理。因此数组 a 可分解为三个一维数组,即 a[0],a[1],a[2]。每一个一维数组又含有四个元素。例如 a[0]数组,含有 a[0][0],a[0][1],a[0][2],a[0][3]四个元素。
(2)从二维数组的角度来看,a 是二维数组名,a 代表整个二维数组的首地址,也是二维数组 0 行的首地址。
特别注意a+1表示第1行的首地址,而不是第0行第第1列的地址,这是初学者最容易犯错的地方。
同样a[1]也是第1行一维数组的数组名和首地址。
所以a+1、*(a+1)、[1]是等价的。
(4)在二维数组中不能把&a理解为元素 a的地址,因为a不是一个数组元素,a是一种地址计算方法,它本身就表示数组 a 第 i 行首地址。所以&a和 a是等价的。这一点也是初学者最容易犯错的地方。
(5)从上边的分析我们得知a[0]+1是 a[0]的 1 号元素首地址,由此可得出 a+j 则是一维数组 a的 j 号元素首地址,它等于&a[j]。
由 a=*(a+i)得 a+j=*(a+i)+j。由于*(a+i)+j 是二维数组 a 的 i 行 j 列元素的首地址,所以,该元素的值等于*(*(a+i)+j)。
理解了下面的算法也就理解了多维数组的指针问题:
int main(int argc, char *argv[])
{
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
printf("%d\n",*(*(a+1)+2));
printf("%d\n",*(a[1]+2));
}
程序输出如下:
6
6两个表达式都输出a[1][2]的值。*(a+1)则表示二维数组a的第1行,也就是等价于a[1]。a[1]、*(a+1)都表示数组的第1行。所以*(a[1]+2))和*(*(a+1)+2)都表示数组的第1行的第2个元素的值。
有的同学会问了,既然“*(a+1)”和“a+1”是等价的,为什么“printf("%d\n",*((a+1)+2)))”输出结果是错误的呢?
编译器在编译“*((a+1)+2)))”的时候会把“(a+1)+2”优化成“a+3”,因此“*((a+1)+2)))”就变成了“*(a+3)”,也就是a数组第3个一维数组的首地址,显然这个只是一个地址,并不是我们想像中的a[1][2]的值,所以输出了一个非常大的数。为了避免编译器的这种误解,建议大家表示“二维数组a的第1行”的时候用a[1]或者*(a+1),而尽量不要用(a+1)因为很容易出错。
9、函数指针
在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。
函数指针变量定义的一般形式为:
函数的返回值的类型 (*指针变量名)(参数列表);
其中“参数列表”可以省略,不过建议明确标明“参数列表”。
例子:
void PrintIt(int i)
{
printf("%d\n",i);
}
int main(int argc, char *argv[])
{
int i = 0;
int arr[5] = {3,5,8,2,1};
void (*myAction)(int);
myAction = PrintIt;
for(i=0;i<sizeof(arr)/sizeof(int);i++)
{
myAction(arr);
}
}
上面的程序遍历数组arr的所有元素,并且打印每个元素。
有的同学会说,这样做有什么意义吗?把“myAction(arr)”直接换成“PrintIt(arr)”不就得了吗?
这么替换在这里是非常合理,也是非常正确的,但是有一天我发现很多地方都要遍历数组做不同的事情,为了避免每次都写for循环,我将遍历数组的功能抽取到一个单独的公共函数中完成
void eachItem(int* pArray,int len,void (*action)(int))
{
int i = 0;
for(i=0;i<len;i++)
{
//调用函数指针
action(*(pArray+i));
}
}
注意函数eachItem的最后一个参数为函数指针类型。
这样main函数就可以简化成如下的样子了:
void PrintIt(int i)
{
printf("%d\n",i);
}
int main(int argc, char *argv[])
{
int arr[5] = {3,5,8,2,1};
int len = sizeof(arr)/sizeof(int);
eachItem(arr,len,PrintIt);
}
以后在其他的地方想对int数组做其他处理,那么只要写不同的函数就可以了,比如说要将数组中的奇数输出:
void PrintOdd(int i)
{
if((i%2)==1)
{
printf("%d是奇数\n",i);
}
}
在main函数中如下调用即可:
eachItem(arr,len,PrintOdd);
可以看到通过函数指针,将循环遍历算法和具体的处理算法隔离了,实现了代码的复用。
函数指针在编程中有非常多的应用。函数指针有时候又被称为回调,在事件机制、模板算法等场合有着广泛应用,因此一定要掌握好。MFC、STL等流行的框架中都大量的应用了函数指针,在JSJ321.com的《C语言也能干大事》系列在线教学中也将进一步讲解函数指针的更生动的应用。
附录:本小节用到的代码:
void PrintIt(int i)
{
printf("%d\n",i);
}
void PrintOdd(int i)
{
if((i%2)==1)
{
printf("%d是奇数\n",i);
}
}
void eachItem(int* pArray,int len,void (*action)(int))
{
int i = 0;
for(i=0;i<len;i++)
{
action(*(pArray+i));
}
}
int main(int argc, char *argv[])
{
int arr[5] = {3,5,8,2,1};
int len = sizeof(arr)/sizeof(int);
eachItem(arr,len,PrintIt);
eachItem(arr,len,PrintOdd);
}
10、指针数组、指针的指针。
指针数组的经典用途就是声明字符串数组:
static char *name[]={"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"};
这两部分请同学们参考教材。
11、结构指针
对结构的访问一般形式为:
结构变量.成员名
(*结构指针变量).成员名
或为:
结构指针变量->成员名
应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。
12、结构指针变量作函数参数
允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。
13、动态存储分配
注:“C语言中不允许动态数组”是C89标准中的规定,所以TC、VC6等C89等老的编译器会有这个问题,新的C99中已经不存在这个问题。
C语言中不允许动态数组类型。例如下面的代码是错误的:
int n;
scanf("%d",&n);
int a[n];
但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据。对于这种问题,用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收。
(1)分配内存空间函数 malloc
调用形式:
(类型说明符*)malloc(size)
功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。
(2)释放内存空间函数 free
调用形式:
free(void*ptr);Sample Text
c语言指针疑惑[转载]的更多相关文章
- (转载)c语言指针学习
前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...
- C语言指针入门知识
C语言指针往往是C语言学习过程中最困难的地方, 最近重新理解了一下C语言的指针知识, 在此整理一下, 如果有错误请留言指正. 对于刚入门的人来说, 指针涉及方方面面, 从简单的数组到结构体, 都会用到 ...
- 关于C语言指针的一些新认识(1)
Technorati 标签: 指针,数组,汇编,C语言 前言 指针是C语言的精华,但我对它一直有种敬而远之的感觉,因为一个不小心就可能让你的程序陷入莫名其妙的麻烦之中.所以,在处理字符串时,我总是能用 ...
- C语言指针转换为intptr_t类型
1.前言 今天在看代码时,发现将之一个指针赋值给一个intptr_t类型的变量.由于之前没有见过intptr_t这样数据类型,凭感觉认为intptr_t是int类型的指针.感觉很奇怪,为何要将一个指针 ...
- [转]C语言指针学习经验总结浅谈
指针是C语言的难点和重点,但指针也是C语言的灵魂 . 这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较 ...
- 不可或缺 Windows Native (7) - C 语言: 指针
[源码下载] 不可或缺 Windows Native (7) - C 语言: 指针 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 指针 示例cPointer.h #i ...
- C语言指针学习
C语言学过好久了,对于其中的指针却没有非常明确的认识,趁着有机会来好好学习一下,总结一下学过的知识,知识来自C语言指针详解一文 一:指针的概念 指针是一个特殊的变量,里面存储的数值是内存里的一个地址. ...
- 不可不表的OSG智能指针之强指针与弱指针 《转载》
不可不表的OSG智能指针之强指针与弱指针 <转载> 使用OSG的人都知道OSG的内存管理方式采用了智能指针,通过智能指针的方式让OSG自己处理对象的销毁工作.在OSG中有两个智能指针类型, ...
- 关于C语言指针的问题
在学习关于C语言指针的时候,发现这样一个问题,代码如下: #include<stdio.h> #include<stdlib.h> #include<string.h&g ...
随机推荐
- 什么是CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGI?
什么是CGI CGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上. CGI可以用 ...
- TCP/IP 和 Socket 的关系
要写网络程序就必须用Socket,这是程序员都知道的.而且,面试的时候,我们也会问对方会不会Socket编程?一般来说,很多人都会说,Socket编程基本就是listen,accept以及send,w ...
- Alpha版本十天冲刺——Day 10
站立式会议 最后一天,很高兴我们做出了跟预期差不多的版本,实现了基本功能,虽然还有一些bug,但是下一阶段我们会继续加油! 会议总结 队员 今天完成 遇到的问题 感想 鲍亮 功能细节更改 我的手机运行 ...
- MySQL SQL优化
一.优化数据库的一般步骤: (A) 通过 show status 命令了解各种SQL的执行频率. (B) 定位执行效率较低的SQL语句,方法两种: 事后查询定位:慢查询日志:--log-slow-qu ...
- Eclipse安装Freemarker插件
方法一:手动安装 手动安装没有成功 步骤: 1. 下载freemarker-ide : http://sourceforge.net/projects/freemarker-ide/files/ 2. ...
- java 读文件路径问题
文件路径:右键点击src新建Source Folder,创建结果与src目录同级. C:\Users\lenovo\workspace\timedTask\config\userinfo.proper ...
- linux安装wine
1.添加PPA sudo add-apt-repository ppa:ubuntu-wine/ppa 2.更新列表 sudo apt-get update 3.安装Wine sudo apt-get ...
- js框架设计1.1命名空间笔记
借到了司徒正美的写的js框架设计一书,司徒大神所著有些看不太懂,果然尚需循序渐进,稳扎js基础之中. 第一张开篇司徒阐述了种子模块的概念 种子模块亦为核心模块,框架最先执行模块,司徒见解应包含:对象扩 ...
- SpringMVC客户端发送json数据时报400错误
当测试客户端发送json数据给服务器时,找不到响应路径? 原来是参数类型不符,即使是json也要考虑参数的个数和类型 解决:将age请求参数由"udf"改为"3" ...
- 站点SEO优化日记之设置目标关键词
一.怎样确定关键词? 根据百度指数,选择搜索指数较高的作为候选关键词.一个关键词的搜索结果中,若大部分都为非首页链接,则该关键词可在短时间内通过优化获得好的排名,可选择此类关键词作为目标关键词. 二. ...