学习C语言三,指针(上)
怎么说呢?我想,如果再不写这个东西,后面一段时间可能会比较忙,而且,指针也是C语言中算是“比较”重要的部分了。再说,好多同学也会被指针折腾的死去活来的,这不得互帮互助一下?
一,对指针的一些简单的刨析
一,指针的作用
1.处理复杂数据,
2.能对计算机的内存分配进行控制
***3.在函数的调用中返回多个参数值
二,指针与指针变量
1.指针是什么?
计算机为了对内存单元中的数据进行操作,一般是按地址存取的。也就是说对内存单元进行标识编号。
打一个不恰当的比方,计算机好比一个酒店,而需要存到计算机里面的元素就好比于要住到酒店里面的人,而人要住店的话就需要开一个房,开的那个房就是要存储数据的地址,房号就是地址名。我们可以根据房号而找到人,我们也可以根据地址找到存储的元素值。
程序执行时就是将变量翻译为它所在的内存地址进行操作的。
2.使用变量的方法
1.直接访问变量
int x=89; printf("%d",x);
就是直接找上门去,对它进行访问和使用
直接通过变量地址来读取内容
2.间接访问变量
间接通过指针变量中的内容(即某个变量的地址)进一步通过访问这个地址而读取内容。
#include<stdio.h> int main(){ int a=3,*p; p=&a; printf("a=%d,p=%d\n",a,*p); *p=10; printf("a=%d,p=%d\n",a,*p); printf("Enter a:"); scanf("%d",&a); printf("a=%d,p=%d\n",a,*p); (*p)++; printf("a=%d,p=%d\n",a,*p); return 0; }
3.二者的区别
C语言使用指针对变量的地址进行操作。指针是用来存放内存地址的变量。
如果一个指针变量的值是另一个变量的地址,就称该指针变量 指向 那个变量。
在C语言中,指针被认为是一个概念,是计算机内存地址的代名词之一。而指针变量本身就是变量,和一般变量不同的是,它存放的是地址。大多数情况下,并不特别强调他们之间的差别。
一般都是理解为存放内存地址的指针变量。
三,对指针的一些刨析
我们就不讲那些怎么定义等等那些东西了,太简单了,学到了现在,这些基本的东西还是要懂的。
1.指针变量名必须是一个合法的标识符。
2.指针值可以是特殊的地址0,也可以是一个代表机器地址的正整数。
3.最好的使用数据的习惯,定义完立马就初始化。在指针的使用时,我们就先需要初始化它为空指针,即指向0;
#include<stdio.h> int main(){ int x=5342; int *p=NULL;//为使程序的可读性提高,初始化时为null,其ASCLL码值为0; p=&x; printf("if i know the name of the variable,i can get it'svalue by name:%d\n",x); printf("if i know the address of the variable is:%x,then i also can get it is value by address:%d\n ",p,*p); return 0; }
*p=NULL;
4.数组名与指针的不同,看过前面的数组篇应该知道,数组名就是数组的首元素地址,指针也是某个元素的地址,但是指针的本质是指向地址的变量,然而数组名是一个地址常量,是不能改变的,故类似数组名++的操作是非法的。
*****5.指针类型问题(这个玩意我不标红,也不取标题,就是玩,就是看不惯那些不认真看我写东西的人,哥们写一篇博客要花我两三个小时,想几分钟就草草看完,我怎么想?都没有人评论问问题,是我讲的已经很清楚了吗?都搞懂了?)
这么重要的东西,认真理解。
指针变量的类型不是指指针变量本身的类型,而是它所指向的变量的数据类型。
无论何种类型的指针变量,它们都是用来存放地址的,因此指针变量自身所占的内存空间大小与它指向的变量数据类型无关,尽管不同类型的变量所占的内存空间不同,但是不同类型指针变量所占的内存空间大小都是相同的,看你的电脑,是4个还是8个字节。
***解引用:通过指针,找到对应的内存和内存中的数据。
一般来说,一旦指针变量的类型被确定后,它只能指向同种类型的变量。
相同类型的指针才能赋值,所有的数据类型都这样,类型都不一样,怎么 赋值?会造成数据的丢失。
**指针类型直接决定了指针解引用的权限有多大(有几个字节)和指针走一步能走多远,即指针加一能走多远(几个字节)。
不同数据类型的指针解引用的空间大小不同,自己去上机实践一下,
你可能觉得这也没啥,但是这个是贯穿所有指针的应用情景的。一定要搞清楚。
6.野指针:指向位置不可知(随机/不正确/无明确限制)
1)未初始化:指针变量会获得一个随机地址,不能确定该地址属于谁。也不能确定这个地址是否存在。
2)指针越界:(主要用于数组中)C语言规定,数组可以向后越界,但是不能向前越界,我也不懂为什么,规定。。。
3)指针指向的空间释放:常见于自定义函数
#include<stdio.h> int *test(){ int a=10; return &a;//test函数结束后,a的储存单元释放掉,数据丢失,返回了a的地址,但是地址里面没有值 } int main(){ int *p=test(); *p=20; return 0; }
故,p为野指针。
四.指针的应用
1.指针的运算
1.指针加减常数:代表指针的地址加减
int类型的数据一个占四个字节,再自己算算上面三个地址的差值是多少。
2.指针-指针
这个为啥给你们看呢?因为这是一个错误的演示,指针减指针的运算只能用在同一块空间内,类似数组这样的,指针减指针得到的值就是两指针之间元素的个数。
#include<stdio.h> int main(){ double a[2],*p,*q; p=a; q=p+1; printf("%d\n",q-p); printf("%d\n",(int)q-(int)p); return 0; }
指针加指针是没有意义的,地址加地址啥意义都没有。
3. 指针不等式
比较的就是在同一块空间的左右,拿上面的例子来讲,p2>p1;
这个用的比较少。
4.*p++指的就是先用*p的值再++;跟之前是一样的
2.指针传参
自定义函数返回值只能通过return语句来返回一个值,那我如果要返回多个值怎么办?
指针是一个很好的办法,后面要学的结构体也能做到,但是本质还是通过指针的方法来做到的。
#include<stdio.h> void swap1(int x,int y),swap2(int *px,int *py),swap3(int *px,int *py); int main(){ int a=1,b=2; int *pa=&a,*pb=&b;//任何数据使用前记得要初始化,指针也是。 swap1(a,b); printf("After calling swap1:a=%d b=%d\n",a,b); a=1,b=2;//每次使用a,b的值时,都先初始化,避免a,b的值改变。 swap2(pa,pb); printf("After calling swap2:a=%d b=%d\n",a,b);//只有swap2才能实现变量值的交换 a=1,b=2; swap3(pa,pb); printf("After calling swap2:a=%d b=%d\n",a,b); return 0; } void swap1(int x,int y){ int t; t=x; x=y; y=t;//形参不能改变实参,值是由实参转向形参的,当函数调用结束后,变量借用的存储空间就被收回了,数据清除。 } void swap2(int *px,int *py){ int t; t=*px; *px=*py; *py=t;//进行的是变量的交换,返回的是地址,px,py的地址并没有变。 } void swap3(int *px,int *py){ int *pt; pt=px; px=py; py=pt;//这样不能改变值,形参改变不了实参 }
下面这个例题比较难,就是平年闰年的问题,说实话,很多东西是初学者很难想到的,如果真的认真做的话 ,我觉得自己也做不到这样简洁,这也告诉我们,要多读优秀代码,慢慢学习,逐渐积累,这些在我们面前的代码通常都是前人总结下来的方法,能让我们受益良多。
#include<stdio.h> void month_day(int year,int yearday,int *pmonth,int *pday); int main(){ int day,month,year,yearday; printf("input year and yearday:"); scanf("%d%d",&year,&yearday); //函数声明,传递给自定义函数,指针对应的部分为地址。 month_day(year,yearday,&month,&day); printf("%d-%d-%d",year,month,day); return 0; } void month_day(int year,int yearday,int *pmonth,int *pday){ int k,leap; int tab[2][13]={//列下标定义为13,第一列都是0,让下标与月份对齐。 {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; //合理运用优先级的关系。数组行下标为0或1; leap=(year%4==0 && year%100!=0)||year%400==0; for(k=1;yearday>tab[leap][k];k++){ yearday-=tab[leap][k]; } //最后,把得到的多个值与指针变量替换,通过返回地址,顺便把替换的值输出,从而达到函数返回多个值。 *pmonth=k; *pday=yearday; }
如果要用指针遍历数组的话,一定要把循环学好,循环条件要熟,不然程序改错会很麻烦,
所以我说,不管学什么,基础都是很重要的。这也是我血的教训。
#include<stdio.h> int main(){ int i,n,a[10],*p; long sum=0; printf("Enter n(n<=10):"); scanf("%d",&n); printf("Enter %d integers:",n); for(i=0;i<n;i++) scanf("%d",&a[i]); for(i=0;i<n;i++) sum+=*(a+i);//也可sum+=a[i]; printf("sum=%ld",sum); sum=0; for(p=a;p<a+n;p++)/*用指针p来作为循环条件, p=a代表首地址相同, p<a+n代表p指针代表的地址不会超过从a首地址开始往后面数n个单元的长度 */ sum+=*p; printf("sum=%ld",sum); return 0; }
#include<stdio.h> #define MAXN 10 void swap(int *px,int *py); void bubble(int a[],int n); int main(){ int n,a[MAXN],i; printf("Enter n(n<=10):"); scanf("%d",&n); printf("Enter %d integers:",n); for(i=0;i<n;i++) scanf("&d",&a[i]); bubble(a,n);//用数组名即可。 printf("After sorted:"); for(i=0;i<n;i++) printf("%3d",a[i]); return 0; } void bubble(int a[],int n){ int i,j,t; for(i=1;i<n;i++){ for(j=0;j<n-i;j++){ if(a[j]>a[j+1]) swap(&a[j],&a[j+1]); } } } void swap(int *px,int *py){ int t; t=*px; *px=*py; *py=t; }
这里还是要主要的讲一下,讲指针作为参数传递时,一般还要将长度也传过去,因为有数组一般就都要遍历(后面会深讲)
#include<stdio.h> #define N 20 void p_sort(int *p,int n){ int i,j; for(i=0;i<n;i++){ for(j=1;j<n-i;j++){ if(a[j]<a[j+1]){ temp=a[j]; a[j]=a[j+1]; a[j+1]=a[j]; } } } } void p_output(int *p,int n){ for(int i=0;i<n;i++) printf("%d",p++); } int main(){ int a[N],n; scanf("%d",&n); for(int i=0;i<n;i++) scanf("%d",&a[i]); p_sort(a,n); p_output(a,n); return 0; }
还是那句话,我给的例子对你们理解这些东西是很有帮助的,不要问我为什么不给题目,学习编程,我觉得更好的还是学思维,就算上面的例子你不知道它讲的什么都不要紧,但是要理解思维。我不想给题目你们的原因还有一个就是,如果你把这些例子搞通了之后,自己也会知道这些是什么题目的例子。前提是要认真揣摩,学习是孤独且枯燥的,望诸君牢记!!!
好了,现在我再来讲一讲指针传参的原理
指针传参的意思就将指针作为函数的参数,那么我们的实参是什么呢?因为指针的本质就是地址,所以实参部分也必须是地址。
那为什么传地址过去自定义函数的内容就能改变主函数中的值呢?
首先,我们要搞清楚,为什么形参不能改变实参,因为自定义函数结束之后系统会将自定义函数中的变量空间释放,所以不管你做了什么都白搭。
但是现在,我传过去的是地址,你可以把存储这个地址的变量空间释放,但是,无法把这个地址中的空间也释放。反而可以通过这个地址来间接访问变量。等自定义函数结束之后,也对这个地址内的变量值无法造成任何影响。
五.字符串处理函数
我本来想出一个专题的,太懒了,先写这些,如果有人需要,我再看什么时候专门出一个课外拓展。
为什么在这里讲字符串处理函数而不在函数里面讲呢?
因为我们主要要理解的是它的模拟实现,从而学会一些思维和操作。
1.strlen函数
给了三种方法
#define _CRT_SECURE_NO_WARNINGS 1 //计数器 #include<stdio.h> #include<assert.h> int my_strlen(const char* dest) { int count = 0; assert(dest); while (*dest++) { count++; } return count; } int main() { char arr[] = "hello"; printf("%d",my_strlen(arr)); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 //递归 #include<stdio.h> #include<assert.h> int my_strlen(const char* dest) { assert(dest); if (!* dest) return 0; return 1 + my_strlen( dest + 1); } int main() { char arr[] = "hello"; printf("%d", my_strlen(arr)); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 //指针-指针 #include<stdio.h> #include<assert.h> int my_strlen(const char* dest) { assert(dest); const char* p = dest; while (*p) { p++; } return p - dest; } int main() { char arr[] = "hello"; printf("%d", my_strlen(arr)); return 0; }
我们看一下这些代码,有个地方可能看不懂,那个assert是什么意思,是断言的意思,就是我们向系统保证,它一定是我们说的那样,后面括号内的表达式一定为真,如果为假,系统会输出错误的原因,自己上机了解,这个很简单,还有一个就是要引一个assert.h的头文件。
再讲一下那个const;
const的作用是修饰变量,它修饰过的变量就叫做常变量,啥叫常变量?
有着常数的性质,不能修改,但是本质还是一个变量。
2.strcmp函数
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<assert.h> int my_strcmp(const char* p1, const char* p2) { assert(p1 && p2); while (*p1++ == *p2++ && *p2) { return 0; } return *p1 - *p2; } int main() { char arr1[20] = "hello "; char arr2[] = "world"; int ret=my_strcmp(arr1, arr2); if (ret == 0) { printf("arr1=arr2"); } if (ret < 0) { printf("arr1<arr2"); } if (ret > 0) { printf("arr1>arr2"); } return 0; }
3.strcpy函数
#include<stdio.h> #include<assert.h> void my_strcpy(char *dest,const char* src){//保证src不会改变 assert(src);//断言 assert(dest); while(*dest++=*src++){//此处要对两个指针分别进行++操作,故两个指针不能为空(NULL) ;//当src结束时(src=0),表达式为假,循环结束。 } } int main(){ char arr1[10]="xxxxxxxxx"; char arr2[10]="hello"; my_strcpy(arr1,arr2); printf("%s",arr1); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<assert.h> char* my_strcpy(char* dest,char * src) { assert(dest && src); char* p = dest; while (*dest++ = *src++); return p; } int main() { char arr1[20] = "hello "; char arr2[] = "world"; printf("%s\n",my_strcpy(arr1, arr2) ); return 0; }
虽然说,没有返回值也行,但是实际上都是有返回值的,
4.strcat函数
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<assert.h> char* my_strcat(char* dest, const char* res) { assert(dest && res); char* p = dest; while (*dest) { dest++; } while (*dest++=*res++) { ; } return p; } int main() { char arr1[20] = "hello "; char arr2[] = "world "; printf("%s\n", my_strcat(arr1, arr2)); return 0; }
讲一下这个,当时这个例子也有一个点迷惑了我,就是while部分。
两个while的含义是不同的,第一个while就算为假也会进行++操作,而下面那个不会,有人知道为什么两个地址就差一个吗?这就是上面说的解引用,char类型的指针,就占一个字节。
讲一些细节。
首先,字符串处理函数一般都是会‘\0’下手的。
再一个,被处理字符串一定要够长,空间一定要足够,避免越界。
还有一个很重要的东西,就是‘\0’是很重要的,如果字符串没有‘\0’,这个就不能叫字符串。
不然后面肯定会因为这个吃亏,没事,吃亏是福,我就喜欢看你们吃亏。
strcmp是字符串比较大小,并不是说把字符串里面的字符一家然后去比较,也不是哪个长哪个就大,第一个先比,看谁大,谁大谁所在的字符串就大,如果第一个字符相等,那就比第二个,跟第一个一样,谁大谁所在的字符串就大。
#include<stdio.h> #define N 20 #include<string.h> int main(){ int i,n; char s[N][N]; printf("请输入n:"); scanf("%d",&n); getchar();//scanf这有一个回车符哦!!! printf("请输入%d个字符串:\n",n); for(i=0;i<n;i++){ gets(s[i]); }//二维数组的输入,只需循环行数,但列数要定义的足够大 int max=0; for(i=1;i<n;i++){ if(strcmp(s[max],s[i])<0) max=i; } printf("最大的字符串为:%s",s[max]); //也可用puts(s[max]) return 0; }
再给一个压缩字符串的题,这个是很难的哦,选做。。。
#include<stdio.h> #define MAXN 80 void zip(char *p); int main(){ char line[MAXN]; printf("Input the string:"); gets(line); zip(line); puts(line); return 0; } void zip(char *p){ char *q=p; int n; while(*p!='\0'){ n=1; while(*p==*(p+n)){ n++; } if(n>=10){ *q++=(n/10)+'\0'; *q++=(n%10)+'\0'; } else if(n>=2){ *q++=n+'\0'; } *q++=*(p+n-1); p+=n; } *q='\0'; }
六,字符串和字符指针
这个东西我就随便讲讲了,虽然重要,但是不难。
字符串常量在内存中的存放位置由系统自动安排。字符串的存储,其实就是一个特殊的一维数组。与数组类似,字符串常量中的所有字符在内存中连续存放。字符串常量的值就是存放字符串常量首字符的存储单元的地址。
字符串常量实质上就是一个指向该字符串首元素的指针常量。是不是有点熟悉,嘿嘿嘿,就是数组名。
如果定义一个字符指针接收字符串常量的值,该指针就指向字符串的首字符,这样,字符数组和字符指针都能用来处理字符串。
这次就说这么多了,后面还会出一期更深的指针。
希望大家能从中学到东西,静待大家的斧正和指教。