【C_Language】---最全面的C指针总结,初级程序员必备
好久没写博客了,重新学习C语言了的基础课程,发现很多东西都忘记的差不多了,闲来无事,总结一下关于指针的知识,希望能帮到像我一样的菜鸟们:
指针,众所周知是C语言的精华所在,不懂指针的话,你就不要说你学过C语言,因为实在是太丢脸了。
很多人,(当然,也包括我在刚学习指针的时候,被灌输的思想就是指针就是地址)然而,这仅仅是个部分正确的说法。指针与地址有着千丝万缕的联系,然而它又不完全同于地址,具体是什么,且听我慢慢道来。
一、指针与地址的关系
什么是地址?比如,你的内存有1K的存储空间,那么按字节给你的内存进行编号,你的内存将被划分称为1024个空间,编号0-1023,这就是你的内存地址。
什么是指针:首先它是一种数据类型,你可以这样认识它:指针就是一个变量,这个变量的值就是一个地址,请注意,该变量的值是一个地址。 请看下面例子。
int a = ;
int *p= &a;
printf("a的地址是:%d",&a);printf("a的值是:%d",a);
printf("指针的地址是:%d",p);printf("指针所指空间的值:%d",*p);
0x00 | a = 3 |
0x01 | |
0x02 | p = &a=0x00 |
0x03 |
结合代码和示意图,定义了一个变量a,变量a的地址是0x00,而0x00地址中的数据是3,即a = 3,&a = 0x00;同理定义了一个指针变量p,p的地址是0x02,而0x02地址中的数据是p的值,既p = 0x00,&p = 0x02,也就是p的值是a的地址,那么*p,就是将0x00地址中的数据取出来,则*p=3;则以上代码的输出结果:
a的结果是:0x00; a的值是3;
指针的地址是:0x02; 指针所指空间的值是:3
至此,你应该能理解指针和地址的关系了吧。
二、指针的使用
1.定义
定义方式:类型说明符*指针变量名;
类型说明符:int,char……用来表示指针中所存放的地址中的数据类型。
2.初始化:
int a = 3; int *p = &a;
int str[3] = {0}; int *p = str;
3.赋值:
4.空指针
用NULL初始化的指针 int *p = NULL;
指向0地址单元的指针。指针定义时若没有初始化,就需要赋空。
5.野指针
可以理解为,指向系统未知单元的指针,修改该指针会导致系统崩溃,编程要坚决避免也指针的出现。
三、指针的大小
无论你定义的是什么类型的指针,包括char、int、float等等,你的指针的大小都是4个字节(前提是在32位机器上的编译器)。
四、参数传递
在此要介绍的一个概念就是传值和传址。
首先看一个传值的例子:
int fun(int n)
{
n = ;
} int main(void)
{
int a = ;
fun(a);
printf("a=%d",a);
}
该代码的运行结果是 a = 5,而不是多数人理解的a = 3;原因就在于,你通过形参传递到函数fun()里面的仅仅是一个数值,而原本a地址中的数据并没有发生改变,所以你在调用函数fun之后打印出来的仍然是5,如果想要打印出来的数是3,那么你就需要使用到传址,代码做如下修改:
int fun(int *n)
{
*n = ;
} int main(void)
{
int a = ;
fun(&a);
printf("a=%d",a);
}
这个时候打印出来的就是 a = 3;
在使用指针做函数参数的时候,一定要注意的是传值还是传址。
四、指针与数组
指针与数组的难点无非就两点概念的掌握,一是指针数组,二是数组指针。
指针数组的本质是一个数组,只是数组内的元素都是指针,每个指针都可以只想一个变量的地址,
int main(void)
{
int i = ;
int *a[];
int b = ,c = ,d = ;
a[] = &b;
a[] = &c;
a[] = &d;
for(i=;i<;i++)
{
printf("%d ",*a[i]);
}
}
该程序的输出结果是: 0 1 2
而数组指针的本质仍旧是一个指针,它能指向一个数组,
int main(void)
{
int i = ;
int (*a)[];
int b[][] = {,,,};
a = b;
for(i=;i<;i++)
printf("%d ",*(a[]+i));
for(i=;i<;i++)
printf("%d ",*(a[]+i));
printf("\r\n");
}
该程序的输出结果是:1 2 3 4
对于数组指针你可以这样理解, 对于int(*s)[4]这样的一个数组指针,首先,这样的指针是指向一个int [4]这样的类型的数组,然后,单独的来看(*s),你把他当成是一个s[]这样的数组,那么s[0]就是第一个指针,这个指针指向一个int [4]类型的数组,s[1]就是第二个指针指向另外一个int [4]型的数组。
假设定义了两个数组a[4],b[4],那么你可以用s[0] = &a[0];s[1] = &b[0]这样的方式来定义两个指针,分别指向a,b两个数组。想要取出所指向的a数组四个元素的话,你可采用数组表示发:s[0][0],s[0][1]……,若想取出数组b的四个元素,就是s[1][0],s[1][1]……,至此,相信你应该能够理解什么是数组指针了吧。
五、指针与字符串
学习C语言的都应该知道,我们通常是采用数组的方式来定义一个字符串,同时,C语言允许直接使用指针的方式来定义一个字符串,并且该字符串存储在内存中的文字常量区,生存周期直到整个程序运行结束。举个简单的例子。
# include "stdio.h"
# include "string.h" char * fun1(void)
{
char *s = "szhb";
return s;
} char * fun2(void)
{
static char s[] = "szhb";
return s;
} char * fun3(void)
{
char m[] = {"szhb"};
return m;
} int main(void)
{
char *o,*p,*q;
o = fun1();
p = fun2();
q = fun3();
printf("%s\n",o);
printf("%s\n",p);
printf("%s\n",q);
return ;
}
以上代码在VC6.0编译器下运行的结果是这样的。
也许你会奇怪,fun1()函数为啥没错误,这就要注意上面我所说,C语言允许采用指针的方式来定义一个字符串,该字符串的存放位置以及生存周期,决定了fun2()函数是可以正常运行的,而fun3()错误之处就在于,你在函数体内定义了一个函数了数组,该数组是存储在栈空间上的,当函数运行结束的时候,栈空间被释放,此时你返回的地址,将是一片没有任何意义的地址,如果想用。
五、指针与函数
与数组类似,指针与函数的关于也在于两个概念的理解,指针函数和函数指针。
返回类型为int型的无参数指针函数的定义 :*fun(void) 它的涵义是,该函数有一个返回值,返回值是一个指针,该指针指向一个整型变量的地址。
看以下例子:
int * fun(void)
{
static int a = ;
printf("%d\n",&a);
return &a;
}
int main(void)
{
int *p;
p = fun();
printf("%d\n",p);
return ;
}
该函数的运行结果如下:
从结果可以看出,两次打印出的值是一样的。
int型无参数的函数指针定义:int (*p)(void),本质是一个指针,指向一个函数的入口地址。
看一下例子:
int fun(void)
{
static int a = ;
printf("%d\n",a);
return a;
}
int main(void)
{
int (*p)(void);
p = fun;
p();
return ;
}
运行结果如下:
由结果可以看出,p指向的是函数fun(),的入口地址,执行该地址的函数p();结果同直接执行fun()函数是一样的。
六、多级指针
该部分也是重点掌握一个涵义吧,通常一级指针中存放的是变量的地址,那么二级指针中存放的是一个一级指针的地址,通过该一级指针的地址,再去访问变量的地址,就能访问该变量的具体数值。
例如,
int main(void)
{
int **p;
int *s;
int a = ;
s = &a;
p = &s;
printf("%d\r\n",a);
printf("%d\r\n",*s);
printf("%d\r\n",**p);
return ;
}
该部分代码打出的结果是3个3,p中存放的是指针s的地址,那么*p将会得到s中存放的a的地址,那么*(*p),将a中的地址中的数据再次取出来,就会得到一个3了。
好了,指针部分的知识,就总结这么多了,以后再继续补充个吧,总结的也很一般,一方面为了记录自己的学习之路,另一方面也是希望能帮到广大的博友们。
//*********************************实际中你可能会弄混的指针问题**********************************//
1.
void fun1(int p[])
{
int i = ;
for(i=;i< sizeof(p)/sizeof(p[]);i++)
{
printf("%d ",p[i]);
}
}
int main(void)
{
int str[] = {,,,};
fun1(str);
}
你能知道,最终的运算结果将会打印出数组中的几个数据吗?
最终的结果是,在64位机器上运行时2个数据(1,2),在32位机器上是1个数据(1),为什么呢?虽然我们都知道,对于一个数组str[],是可以采用sizeof(str),来计算该数组的内存大小的,但是当你采用数组作为形参的时候,数组自动退化为一个指针,那么程序就会把形参p当成一个指针,你对一个指针进行sizeof()运算,那么不就是在计算指针的大小吗?64位机器上指针大小是8个字节,32位是4个字节,那么答案也就不言而喻了。
2.指针在二维数组中的应用。
情况一
int main(void)
{
int s[] = {,,,};
int *p1 = (int *)(&s+);
int *p2 = (int *)&s+;
printf("%d %d %d\n",*(s+),*(p1-),*(p2-));
}
该段代码的输出结果是 2,4,1,有没有想明白?
*(s+1)就是s[1]; *(p1-1)就是s[3]; *(p2-1)就是s[0];
对于p1来说,(&s+1)系统认为是在整个数组s[4]的后面地址加1,那么p1-1就是s[3]的地址。
对于p2来说,&s+1不是首地址+1,系统会默认加一个a数组的偏移,是偏移了一个数组的大小,那么p2-1就是s[0]的地址;
原因:&s是数组指针,其类型是 int (s)[5];
情况二
int main(void)
{
int s[][] = {,,,};
int (*p)[] = s;
printf("%d ",p+);
printf("%d\n",p+); printf("%d ",*(p+));
printf("%d\n",*(p+));
return ;
}
这是定义了一个数组指针指向一个二维数组的首地址,但是对于不同的打印方法,打印出来是一样的地址,原因p是一个只想 int [2]的指针,在程序中p指向二维数组s的收地址,p+1是加了一个数组的长度所以p+1应该是s[1]的首地址,假若,p是一个只想int [3] 的指针,那么p+1就应该加12个字节的地址。而打印出的结果相同,也是有点碰巧的意味了。
3.最近在学习的时候,又碰到了一个有趣的关于较为复杂的传址和传值的问题。
void fun5(char **p,int num)
{
*p = (char *)malloc(num);
} int main(void)
{
char *str = NULL;
fun5(&str,);
str[] = ;
printf("%d\r\n",str[]);
free(str);
return ;
}
这是正确的代码,能够输出正确的结果1,跟下面的代码对比一下,值得你推敲一番。
void fun5(char *p,int num)
{
p = (char *)malloc(num);
} int main(void)
{
char *str = NULL;
fun5(str,);
str[] = ;
printf("%d\r\n",str[]);
free(str);
return ;
}
这段代码在编译上没有任何问题,但是运行时,整个程序就崩溃了,这是为何呢?
这里再让我们细细的去考虑一下关于参数的传值和传址吧,在正确的代码中,形参定义的是一个指针的指针,一级*p 是一个地址,主函数我们将指针str的地址传入fun1()函数内,那么函数内的操作相当于改变了str的值,也就是str此时是我们申请的那片空间的地址,但是如果采用,错误的那种写法,形参传入的是一个值,函数内的操作,并没有改变str的值,也就是说,此时主函数中的str仍旧是一个指向NULL的指针,我测试了一下,结果却是如此。
【C_Language】---最全面的C指针总结,初级程序员必备的更多相关文章
- 以技术面试官的经验分享毕业生和初级程序员通过面试的技巧(Java后端方向)
本来想分享毕业生和初级程序员如何进大公司的经验,但后来一想,人各有志,有程序员或许想进成长型或创业型公司或其它类型的公司,所以就干脆来分享些提升技能和通过面试的技巧,技巧我讲,公司你选,两厢便利. 毕 ...
- (1)消灭初级程序员常用的多层if-else嵌套--------------【表驱动法】
表驱动法 1.相信很多刚从事工作的程序员或一些初级程序员在写代码的时候会出现对一些逻辑判断写成多层if-else嵌套的经历,这种方式在一些简单的层次中运用起来确实可行,但对于一些大型项目逻辑判断比较多 ...
- Java进阶之路——从初级程序员到架构师,从小工到专家
原创文章 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到 ...
- 为什么要重写hashcode和equals方法?初级程序员在面试中很少能说清楚。
我在面试 Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分, ...
- C#130问,初级程序员的面试宝典
首先介绍下,目前C#作为一门快速开发的语言,在面试的过程中需要注意的技术知识点,了解下面的知识点对于初级工程师入职非常有帮助,也是自己的亲身体悟. 1. 简述 private. protecte ...
- 最全Oracle环境搭建之.NET程序员初遇Oracle
前言:如果你习惯了傻瓜式的一步步安装,那么Oracle和.NET搭配,绝对会让你头痛不已. 目前我不敢保证自己理解的Oracle理论部分100%正确,但环境安装过程一定可以收藏以备不时之需. 路这么长 ...
- 一个试图了解JVM内存模型的两年经验的初级程序员,透彻!
所有的编程语言中都有内存模型这个概念,区别于微架构的内存模型,高级语言的内存模型包括了编译器和微架构两部分.我试图了解了Java.C#和Go语言的内存模型,发现内容基本大同小异,只是这些语言在具体实现 ...
- 初级程序员应该了解的Linux命令
基于Linux的系统最美妙的一点,就是你可以在终端中使用命令行来管理整个系统.使用命令行的优势在于,你可以使用相同的知识和技能来管理随便哪个Linux发行版. 对于各个发行版以及桌面环境(DE)而言, ...
- <初级程序员> git 的初级使用
作为程序员,Git 是一个很好的代码管理工具.Git 是一个版本控制系统,主要的作用就是记录代码的修改过程,有效的追踪文件的变化.当代码出现错误的时候可以很容易的恢复到之前的状态,不管对于个人开发还是 ...
随机推荐
- java数组简介
数组(Array)是Java 语言中内置的一种基本数据存储结构,通俗的理解,就是一组数的集合,目的是用来一次存储多个数据.数组是程序中实现很多算法的基础,可以在一定程度上简化代码的书写. 备注: 数组 ...
- 【codeforces 761D】Dasha and Very Difficult Problem
time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...
- MD5登陆密码的生成
package com.cinc.ecmp.userpermission.utils; import java.security.MessageDigest;import java.security. ...
- Vue仿网易云PC端的网页
贴个网址:https://github.com/wangjie3186594/-PC- 声明一下:这个网页没做完!没做完!没做完! 本人新人一枚,按照的是我当前的学习进度做的项目,很多效果未 ...
- 【2016常州一中夏令营Day1】
Problem 1. suffix给定一个单词,如果该单词以 er. ly 或者 ing 后缀结尾,则删除该后缀(题目保证删除后缀后的单词长度不为 0),否则不进行任何操作.Input输入一行,包含一 ...
- 025.MFC_窗口操作
窗口操作 一.建立名为dialogOp 的mfc 工程 ,添加9个button 和1个check box组件,并按如图修改caption属性. 最大化窗口 双击最大化button,进入dialogOp ...
- KEIL5.11安装小结
一.注意点 1.安装路径不能带中文,必须是英文路径 2.安装目录不能跟 51 的 KEIL 或者 KEIL4 冲突,三者目录必须分开 3.KEIL5 不像 KEIL4 那样自带了很多厂商的 MCU 型 ...
- 第二阶段:1.流程图:10.visio绘制泳道图
选择跨智能 同时水平 设置好泳道图标题以及泳道名字 泳道位置可以拖拽更换 左侧有一些基本的形状 包括分隔符等等 添加分隔符并填上分隔符上面的阶段名称 左边选择形状界面 然后拖拽使用 同时可以用连接线链 ...
- 怎么彻底删除用友通T3财务软件?
[问题现象]怎么彻底删除用友通T3财务软件? [原因分析]通过"添加或删除程序"无法正常卸载用友通T3,也尝试了360安全卫士强力卸载,都无法完全卸载,有没有办法可以彻底删除用友通 ...
- Hive性能优化(全面)
1.介绍 首先,我们来看看Hadoop的计算框架特性,在此特性下会衍生哪些问题? 数据量大不是问题,数据倾斜是个问题. jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联多次 ...