1、
int a[3] = {2,5,8};
int* p = a;
把数组名赋值给指针表示将数组的首元素的地址赋予此指针。
2、
int a[3] = {2,5,8};
int* p = &a[0];
a[0]是a数组的首元素,而&则是取地址运算符,所以“ &a[0]”取得的同样是a数组的首元素的地址,因此这段代码的含义和代码段是一致的。
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[i]= arr[i]*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[i]);
}
}
运行结果:
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[i]= arr[i]*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[i]= arr[i]*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[i]= arr[i]*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[i]);
}
}
运行以后程序竟然输出了: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[i]理解为元素 a[i]的地址,因为a[i]不是一个数组元素,a[i]是一种地址计算方法,它本身就表示数组 a 第 i 行首地址。所以&a[i]和 a[i]是等价的。这一点也是初学者最容易犯错的地方。
(5)从上边的分析我们得知a[0]+1是 a[0]的 1 号元素首地址,由此可得出 a[i]+j 则是一维数组 a[i]的 j 号元素首地址,它等于&a[i][j]。
由 a[i]=*(a+i)得 a[i]+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[i]);
[/i]}
}
上面的程序遍历数组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等流行的框架中都大量的应用了函数指针,在如鹏网的《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);
 文章来自:http://student.csdn.net/space.php?uid=52781&do=blog&id=4930

杨中科老师谈C语言指针问题快速解惑相关推荐

  1. 自学杨中科老师视频 68.Part3-19:EF Core自引用的组织结构树(Av500515272,P68) 出现错误

    自学杨中科老师视频 68.Part3-19:EF Core自引用的组织结构树(Av500515272,P68) 出现错误 将方法改为如下

  2. 浅谈c语言指针的强制转换

    指针是c语言的灵魂,而数据的强制转换是我们在写程序的过程中经常去使用的一种手段,那么这二者结合在一起后会有什么效果呢? 直接上例子说吧 No.1 上面是一段简单的把变量打印出来的程序,显示指针指向地址 ...

  3. 【杨中科】问苍天,微软的技术更新真的快吗

    经常在网站上看到有人抱怨: "微软的技术怎么更新这么快,.Net2.0.3.0.3.5.4.0.4.5,我的妈呀,都跟不上微软的步伐了!" "还没学会Silverligh ...

  4. c语言指针很危险,浅谈C语言中指针使用不当的危险性.doc

    浅谈C语言中指针使用不当的危险性.doc 第 19 卷 Vol . 19 第 2 期 No . 2 洛阳师专学报 Journal of Luoyang Teachers College 2000 年 ...

  5. c语言弱符号与函数指针,浅谈C语言中的强符号、弱符号、强引用和弱引用【转】...

    首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑,所以写下此篇,希望能和同样 ...

  6. 【解惑】杨中科说给弟弟的话

    刚才和我弟弟通电话聊了聊这一段时间的学习以及下一阶段的学习规划,我弟弟在读大二,是计算机专业,他也是如鹏网上一个比较活跃的会员.今天和他聊电话,感触颇多,倒不是因为我发现了新的东西,而是因为竟然连他都 ...

  7. c语言乐学编程作业答案,信息乐学|浅谈C语言

    原标题:信息乐学|浅谈C语言 一大波C语言的干货正在靠近 刚刚成为大学生的小萌新们,经过两个多月的学习,你们对大学的多彩生活是否还满意?全新的学习方式你们是否还适应?然而,新鲜劲还没过,第一件让你们头 ...

  8. c语言定义node类型指针,C语言指针的概念

    在计算机中,所有的数据都是存放在存储器中的.一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等,在前面已有详细的介绍.为了正确地访问 ...

  9. 专访 | 杨强教授谈CCAI、深度学习泡沫与人工智能入门

    文 | 胡永波 7 月 22 - 23 日,由中国人工智能学会.阿里巴巴集团 & 蚂蚁金服主办,CSDN.中国科学院自动化研究所承办的第三届中国人工智能大会(CCAI 2017)将在杭州国际会 ...

最新文章

  1. BZOJ 4679/Hdu5331 Simple Problem LCT or 树链剖分
  2. NR 5G 非3GPP 接入到核心网络的安全性
  3. python3 执行系统命令
  4. docker远程访问TLS证书认证shell
  5. php编程输出心形图案_利用php输出不同的心形图案,php心形图案_PHP教程
  6. 基于fiddler的网络爬虫校园网自动登陆系统
  7. DualGan 学习笔记
  8. 森拓品牌邀您参加第15届中国模具之都博览会
  9. 本机未装Oracle数据库时Navicat for Oracle 报错:Cannot create oci environment 原因分析及解决方案
  10. Atitit 编程语言知识点tech tree v2 attilax大总结
  11. 10大主流3D建模技术
  12. m1 MacBook 进入自检模式
  13. 计算机硬盘 打开很慢,电脑读写运行速度慢的8种解决办法
  14. COLVERN LM10/3M29电位计春天,宛若初见!
  15. 【论文阅读】 BPR: Bayesian Personalized Ranking from Implicit Feedback
  16. IE8 使用 Oracle ERP
  17. 剩余运算符和展开运算符
  18. 使用mybatis的interceptor修改执行sql以及传入参数
  19. 初级前端工程师课程导航-姜威-专题视频课程
  20. 韦尔奇经典的一次演讲:2001年夏,在股东年会上的讲话(中英文)

热门文章

  1. 在Vue中应用TypeScript
  2. 机器人和人工智能之间,是什么关系?
  3. Java小游戏:冒险岛
  4. 使用Python批量查询ip地址的信息
  5. db2数据库常用语法及命令
  6. 银联POS返回码/错误码详解
  7. 在R语言中如何安装包?
  8. 关于黑洞,那些你所不知道的人和事
  9. 尘梦回还服务器在维护中是什么意思,20191211维护公告解读
  10. 如何让Windows文件管理器滚动如macOS奶油般顺滑?