虽然我是电信专业的,大学期间,学过C,C++,但是因为贪玩,其实啥都不懂,考试能抄就抄,不能抄就交钱补考呗,反正文凭被我混到手了,哈哈! 对于C语言这门学科,基本上我是在毕业1年之后,在工作中被逼无赖才开始学的。之前都是51的汇编哦,哈哈! 因为当时看到别人都用C语言写程序,感觉很高级,很牛掰,所以我当时也好想牛掰一把,呵呵。可能是本人那时候还比较聪明,自从照着别人的C代码做了一个数字钟后,就忽感,原来C语言是这样用的啊,明白了,领悟了,开窍了。就开始了我的C编程的经历! 这几年过来,对于C语言的领悟就是指针的用处很大,很多,很广,还有一点就是,当你的程序写的越多,你会发现程序的结构越来越重要,特别是对于多资源的运行管理这块就是很大的挑战,真是的没有最好,只有更好! 好了,废话不多说了,开始入题吧!
声明一下:其中有些东西写的可能不够严谨,但请同学你不要站牛角尖哦,学习是为了提高的,不是么?
因为常规变量大家都熟悉,也好理解,就从常规变量说起吧,这里我用比较通俗的话来说,不用专有名词,方便大家理解,变量像一个杯子,里面可以放白开水,喝掉白开水还可以倒入果汁,喝掉果汁还可以倒入放酒,说白了就是一个可以装各种液体的容器,那变量就是杯子,里面存放的数据就像是杯中的液体,液体分很多种,你想和哪种就倒哪种液体,随意选择,同样,那数据也分很多种,至于是哪种,就要看你的设计需求了,比如,你要做一个计数器,那你就需要一个可以存放计数值的变量,不然你怎么知道某个时刻计数计了多少呢? 所以必须要一个变量存放计数值,计数值就是变量中的数据。太啰嗦了,呵呵,是不是觉得这简单的东西说的这么繁琐,哎! 我也觉得,呵呵,那就但愿那些刚入门的人可以学到点那个啥吧 !
先说下,所有的变量自身是有地址的,一定要理解这点啦。就像你有家,我也有家一样,并且每个家都有一个唯一的通讯地址门牌号,这样可以网购买东西,直接寄回家来,我经常在网上买元器件的哦,呵呵!
指针是啥呢? 我们常说的指针其实全名叫指针变量,指针变量也是一个变量,和上面说的常规变量没有任何区别的,区别只是这个变量存放的数据是另一个变量的地址而已,和变量自身的地址是没有半毛钱的关系的。这点理解了就很好理解指针了。上面举例说的计数值变量,是指存用来放计数值数据的变量。所以说指针变量和一般变量其实是一样一样的,只是存储的数据名义不同罢了,指针变量里面放的也是数据,只是这个数据值是另外一个变量的地址而已。一般我们常说“某个指针指向谁谁谁”,怎么理解? “某个指针”就是一个指针变量A,“谁谁谁”就是指另外一个变量B, A里面存放的数据值就是B变量自身的地址。
uint8 *pa; // 定义一个指针变量pa,专门用来存放uint8数据类型变量的地址的。
uint8 a; // 定义一个变量a
pa = &a; // 将变量的a的地址值赋给变量pa,那么就说”pa指向了a“。
指针的操作:
一旦指针pa指向a了(即执行了 pa = a之后),就表明可以用pa读写a了,当然a自身也是可以进行读写的。
uint8 *pa; // 定义一个指针变量pa,专门用来存放uint8数据类型变量的地址的。
uint8 a; // 定义一个变量a
uint8 m; // 定义一个变量m
指针的赋值:
pa = &a;
这里&的符号是指取地址的意思,即取得变量a的地址,也即pa指向a
pa = a;
这里没有&的符号,则意思就是将变量a的内容赋给指针变量,而不是取变量a地址,和上面一句有区别的,赋值完了以后,指针变量pa和a没有任何关系了。
指针的读写:
pa = &a; // pa指向a
*pa = 200;// 通过指针pa写入数据200到a,和 a = 200的效果是一样的,因为上面的pa已经指向了a。理解就是pa间接的写数据到a
m = *pa; // 通过指针pa从a中读取数据,然后赋值给变量m,和m=a效果是一样的,实际上就是pa指针间接的读数据到a
指针指向的变量的数据类型:
uint8 *pa; // 定义一个指针变量pa,用来存放uint8数据类型变量的地址的。
uint16 *pb;// 定义一个指针变量pb,用来存放uint16数据类型变量的地址的。
uint8 a;//定义一个uint8变量a
uint16 b;//定义一个uint16变量b
pa = &a; //将变量的a的地址值赋给变量pa,那么就说”pa指向了a“。
*pa = 200;//通过指针pa写数据200到a,和 a = 200的效果是一样的,因为pa已经指向了a。理解就是pa间接的写数据到a
*pa = 1000;// 数据溢出,因为 pa 只用来操作uint8的数据,显然1000已经超过了uint8的范围。这是不可靠的哦。
定义一个指针变量pa,它用来指向/装载8位无符号数据类型变量的地址,在用指针进行访问时(*pa = 200),是按照定义pa时(uint8*pa)的指向数据类型(uint8)进行运算操作的。如 *pa = 1000;这就是错误的,因为已经溢出了。千万不要将 uint8 *pa 理解成pa指针变量自身是uint8类型的。如果是uint16 *pa, 也是一样的道理,指针变量pb,它用来指向/装载16位无符号数据类型变量的地址的.这里的uint8 *pa; uint16 *pb; 中的uint8,uint16是指被指向变量的数据类型,和指针变量本身的占据的长度是没有半毛钱的关系哦,千万要理解了。至于指针变量pa,pb变量本身占据的数据长度是多少,这个和硬件相关的,先理解了这个,后面的慢慢来,咱们先不做讨论。
指针可以指向任何数据类型,任何的内存存储单元的地址。
为什么要用指针呢? 一个句话,为了方便访问呗,至于具体怎么个方便法,一句话还真说不清楚,先举个简单的例子吧。 假如需要统计3个同学的各科考试分数总分,代码如下:
typedef struct
{
uint8 yuwn; //语文成绩分数
uint8 shuxue; // 数学成绩分数
uint8 yingyu; // 英语成绩分数
}student; // 定义一个学生成绩的结构体
student zhangshan = { 70,80,90,};// 定义张三同学成绩的结构体变量实例,并赋予初值
student wangwu = { 75,83,91,}; // 定义王五同学成绩的结构体变量实例,并赋予初值
student lisi = { 79,84,97,}; // 定义李四同学成绩的结构体变量实例,并赋予初值
uint16 zhangshan_sum; // 存放张三的总分的变量
uint16 wangwu_sum; // 存放王五的总分的变量
uint16 lisi_sum;// 存放李四的总分的变量
//====不用指针,用直接的方法实现====================//
uint16 zhangshan_sum_calc(void)
{
uint16 sum;
sum = zhangshan.yuwen; //对于结构体变量对于成员的访问是用.符号的,这是语法,不高深的,不明白就看看书去。
sum += zhangshan.shuxue;
sum += zhangshan.yingyu;
renturn sum;
}
uint16 wangwu_sum_calc(void)
{
uint16 sum;
sum = wangwu.yuwen;
sum += wangwu.shuxue;
sum += wangwu.yingyu;
renturn sum;
}
uint16 lisi_sum_calc(void)
{
uint16 sum;
sum = lisi.yuwen;
sum += lisi.shuxue;
sum += lisi.yingyu;
renturn sum;
}
void mian(void)
{
zhangshan_sum = zhangshan_sum_calc();
wangwu_sum = wangwu_sum_calc();
lisi_sum = lisi_sum_calc();
while(1);
}
//=====使用指针,可以如下实现:================//
// 形参: pstudent 是成绩结构体指针变量,专门用于操作student数据类型变量的
uint16 sum_calc(student *pstudent)
{
uint16 sum;
sum = pstudent->yuwen; //对于结构体指针对于成员的访问是用->符号的,这是语法,不高深的,不明白就看看书去。
sum += pstudent->shuxue;
sum += pstudent->yingyu;
renturn sum;
}
void mian(void)
{
zhangshan_sum = sum_calc(&zhangshan );//&是取变量的地址的符号,这是语法,不高深的,不明白就看看书去。
wangwu_sum = sum_calc(&wangwu);
lisi_sum = sum_calc(&lisi);
while(1);
}
//============================================================//
可以看得出,程序简洁,易懂,灵活。
实际上指针这样的应用,是对结构体实例变量首地址进行寻址,然后按照结构体实例变量首地址的相对地址进行成员的访问。
其中的3个成员变量,在内存中是按照一定格式和顺序分配的,比如分配在连续3个8位内存单元中。
假如 zhangshan 结构体实例变量首地址从1000H开始的,那么
zhangshan.yuwn 就在1000H内存中
zhangshan.shuxue 就在1001H内存中
zhangshan.yingyu 就在1002H内存中
假如 wangwu 结构体实例变量首地址从2000H开始的,那么
wangwu .yuwn 就在2000H内存中
wangwu .shuxue 就在2001H内存中
wangwu .yingyu 就在2002H内存中
假如 lisi 结构体实例变量首地址从3000H开始的,那么
lisi .yuwn 就在3000H内存中
lisi .shuxue 就在3001H内存中
lisi .yingyu 就在3002H内存中
那么相对地址就是指成员变量的地址相对于结构体变量的的首地址的差,也叫偏移地址或者偏移量,
比如zhangshan结构体实例变量首地址=1000H,
则yuwn成员变量与首地址的间隔(偏移地址)是=1000H-1000H = 0H
则shuxue成员变量与首地址的间隔(偏移地址)是=1001H-1000H = 1H
则yingyu成员变量与首地址的间隔(偏移地址)是=1002H-1000H = 2H
要用指针实现,说白了,就是个简单的数学问题了。因为偏移地址是在编译的时候,由编译器来决定和计算的,无需手动计算。所以偏移地址实际就是固定不变的常量了。那现在,只要给出结构体变量的的首地址(比如&zhangshan),那不就可以通过这个首地址+偏移地址定位到成员变量了吗?
你理解没有? 那好吧!看看这个函数
uint16 sum_calc(student *pstudent)
{
uint16 sum;
sum = pstudent->yuwen;
sum += pstudent->shuxue;
sum += pstudent->yingyu;
renturn sum;
}
假设 pstudent 指针的值 = 1000H( zhangshan 结构体实例变量首地址 ),则
sum = pstudent->yuwen;// 就是指将1000H+0地址中内容赋给sum, 0是成员变量yuwen偏移地址,编译器已经编译固定了
sum = pstudent->shuxue; // 就是指将1000H+1地址中内容赋给sum, 1是成员变量shuxue偏移地址,编译器已经编译固定了
sum = pstudent->yingyu; // 就是指将1000H+2地址中内容赋给sum, 2是成员变量yingyu偏移地址,编译器已经编译固定了
假设 pstudent 指针的值 = a( 某结构体实例变量首地址 ),则
sum = pstudent->yuwen;// 就是指将a+0地址中内容赋给sum, 0是成员变量yuwen偏移地址,编译器已经编译固定了
sum = pstudent->shuxue; // 就是指将a+1地址中内容赋给sum, 1是成员变量shuxue偏移地址,编译器已经编译固定了
sum = pstudent->yingyu; // 就是指将a+2地址中内容赋给sum, 2是成员变量yingyu偏移地址,编译器已经编译固定了
还没有理解吗? 无语了,我都写累了,你再重头看一遍吧! 呵呵!
如果你对于上面的都理解了话,基本上就没有啥问题了,当然,指针还有很多的用法,这里只是抛砖引玉了。
关于指针的指针,有点饶人,我也懒得写了,你自己想想呗,不懂的话,拿个笔画一画,要不了多少时间,要不了多少墨水的!
萝莉啰嗦的,不知道你懂了没有,但愿能帮到你!有需要可以和我留言的哦,有时间再写吧!今天好累了!哎!