指针
指针是c语言的精髓,也是c语言的难点。下面是我学习途中整理的一些笔记。
1、指针和地址
和指针相关的两个运算符:
&:取地址运算符 *:取指针所指向的内存单元的值
char c='a'; char *p=&c;
析:这个指针变量用于存放char类型变量的地址。定义的时候进行了初始化,把变量c的地址赋给了变量p,&c是用去取的变量c的地址。则变量p存放的是一个地址,此时我么说变量p指向了变量c。
char c2=*p+1
这儿的*p 的含义是取得指针变量p 所指向的变量的值,也就是取地变量c的值'a',因此变量c2的值为'b'。
注意:char *p 定义了一个指向char型变量的指针,p存放的是一个char型变量的地址。而之后的*p是取的指针p所指向的变量的值,&c取的是变量c的地址。可以理解为 “指针= =地址”
2、指针的大小
在32为编译器下,指针大小都是32位(4字节),在64位编译器下,指针大小都是64位(8字节)
3、指针的定义和使用
c语言中的指针是专门用来存放内存地址的变量,每一个指针都有一个与之相关的数据类型,该类型决定了指针所指向的变量的类型,如一个char型指针只能指向char型变量。
指针定义的一般形式:数据类型 *指针变量名
①char *pc ②int *pi,p
第1个和第2个的指针在定义的时候未进行初始化,因此他们所指向的变量都是未定的。使用未初始化的指针通常会导致出错,应该绝对避免使用未初始化的指针。
char *p; int i=*p;
如上的指针并未进行初始化,指针p并没有明确的指向某个变量,在此情况下要取得p所指向的变量的值是很危险的。程序运行是很容易奔溃。
注意:避免使用未初始化的指针。在定义指针时最好将他初始化为null,即明确当前指针不指向任何变量。
例:int a=10;
int * p ;
*p = &a; //这行代码导致内存问题,它是操作野指针所指向的内存
这样的程序在运行中是会崩溃的。p没有初始化,它的值是一个随机数。*p不是操作的p本身内存,是操作的p所指向的内存,操作野指针所指向的内存。正常的写法是:p=&a
注意:所谓的空指针只是为了做一个标志,标志着这个变量没人使用。千万不要去操作空指针所指向的空间。给这个指针变量赋值为null,null其实也就是数字0
4、指针的操作
同类型的指针变量可以相互赋值
int a=10; int * p1; p1 =&a; int *p2=p1; //同类型的指针变量相互赋值 *p2 = 222; printf("a=%d\n",a); //可以有多个指针变量指向同一块内存
返回局部变量的地址(栈区空间)
1 in func() 2 { 3 int a =10; //局部变量 4 return &a; 5 } 6 7 int main() 8 { 9 int *p; 10 p=func(); //内存会出问题 11 *p=111; 12 return 0; 13 }
解析:1、定义在{}内部的变量为局部变量,局部变量由系统来分配空间,离开{}后系统自动回收,用户不能在使用这块空间。
2、p=func(),相当于把&a赋值给p ,p指向a的地址。但是func()函数调用完毕后,a会自动释放,空间被系统回收,用户不能在操作
3、*p=111 , 把 111 赋值给p指向的内存空间,这中操作是不行的,就是上面说的野指针问题
堆区空间的使用
只要程序不结束,只要不手动区释放内存,空间是不会被回收的
int * func() { int * p1; //1、p指向堆区空间,堆区空间需要调用malloc函数返回 //2、只要程序不结束,用户不free,堆区空间就一直存在 //3、int * 是指向int 型的变量,所以需要分配sizeof(int)大小的空间 p1=(int *)malloc(sizeof(int )) ; return p1; }
int main(void) { int * p2; p2 = func(); //通过函数返回堆区空间地址 if(p2==null) //空间分配失败
{ printf("p2==null\n"); return -1; }
*p2 =111;
printf("p2=%d\n",*p);
//1、一个malloc对应一个free
//2、free(p)不是释放p的空间,而是释放p所指向的堆区空间
//3、free完的空间不能在使用,同一块内存只能释放一次 free(p); p= null; }
解析:1、从main函数开始,定义了一个指向int型变量的指针,然后把func()函数的返回值p1赋值给p2。
2、p1申请了一块堆区空间,假设空间大小为0xaabbb,func函数返回p1,则p2的值也为0xaabbb。
3、指针变量p2也指向堆区空间。当func调用完毕,func内部的p1自动释放,但是堆区空间不释放。
4、 *p2=111,其实是给p2指向的堆区空间赋值,用完之后需要释放空间。free(p)释放的不是p的空间,而是p所指向的堆区空间
5、指针和数组
int a[]={2,4,6,8,10}
int *p1=a;
int *p2=&a[0];
指针指向数组a的第一个元素,即a[0],指针p2也是如此。指针变量p1和p2存放的都是a[0]的地址。
在指向数组某个元素的指针上加上或减去一个整型数值,就可以指向另外一个数组元素,前提是没有超出数组的范围。如下:
p1=p1+3此时p1指向a[3],存放数组元素a[3]的地址。
注意:1、在指针上进行加减运算后所得到的指针,必须指向同一个数组或指向数组存储空间的下一个单元。
2、不能对数组名执行++,- -操作,比如a++是不合法的,这是因为a是数组名,它是数组的首地址,它的值在程序运行过程中是固定不变的,是常量
数组元素也可以是指针类型,这种数组称为指针数组也就是说指针数组的每一个元素都是指针变量。
指针数组定义的一般形式为:类型名 *数组名[数组长度] 如:char *a[5]
运算符*的优先级低于运算符[],因此a先与[5]结合,形成a[5]的形式,它显然是一个数组。然后在与*结合,表示数组元素的类型为指针,每个数组元素都指向一个整型变量。这里的a就是一个二级指针,它表示指针的指针。
#include<stdio.h> int main() { int a[2][5]={1,3,5,7,9,2,4,6,8,10}; int (*p)[5],i; p= a; for(i=0;i<5;i++) { printf("%d",(*p)[i]); } printf("\n"); p++; for(i=0;i<5;i++) { printf("%d",(*p)[i]); } printf("\n"); return 0; }
输出结果:1 3 5 7 9
2 4 6 8 10
解析:1、int (*p)[5],表示p是一个指针,它指向含有5个元素的一维数组,p也只能指向一个包含5个元素的一维数组,p就是该一维数组的首地址。这里*p两边的括号是不可少的,因为[]的优先级bi*高。
2、p= a,p指向二维数组a的第一行。然后通过(*p)[i]访问该行的每一个元素
3、p++,使p指向二维数组的a的第二行。
注意:区别int(*p)[5]和int *p[5]。前者是一个指针,它指向一个含有5个元素的数组。后者是一个数组,它的长度为5,数组中每一个元素指向一个整型变量。
6、指针和函数
指针做函数的参数
函数的参数不仅可以是整型、字符型、实际上也可以是指针类型。它的作用是将一个变量的地址传送到函数中。
#include<stdio.h> #include<string.h> #include<stdlib.h> void change(int i,int *p) { i++; if(p !=null) (*p)++; } int main() { int a=5,b=10; change(a,&b); printf("a=%d,b=%d\n",a,b); return 0; } 输出结果 a=5,b=11
解析:1、主函数调用了change函数中,一个实参为变量a,另一个实参为变量b的地址。因为change函数的第二个参数为指针,所以必须传递一个变量地址。对于指针型实参,参数也可以是null,因此change函数中必须检查 p是否为null,如果实参为null,那么语句(*p)++将导致程序崩溃。 2、函数的参数是局限于该函数的局部变量,函数调用时,系统为函数的局部变量分配内存。在主函数中调用change时,系统分配了8个字节的内存,4个字节保存变量i的值,4个字节保存指针变量p的值。i保存的是数值5 而指针变量p保存的是变量b的地址。i++并不是作用于变量a,而是作用于刚刚分配好的4字节存储空间,此时i的值为6而a的值依然是5。 3、对于(*p)++,p保存的是变量b的地址,*p的含义是系统根据地址找到变量b,然后对它执行加1操作,然后b的值就变成了11,函数调用结束后,系统分配的内存也就被回收了。 注意:如果一个函数的参数中有指针,为了程序的健壮性,在函数中需要检查参数是否为null。
7、指向字符串的指针
c语言中访问一个字符串有多种方法。
①、用字符数组存放一个字符串
char string[ ] = "linux c";
printf("%s\n",string);
string是一个字符数组名,它同时也是该字符数组的首地址,也就是“linux c”这个字符串的首地址
②、用字符串指针来指向字符串(字符串指针)
char *p="linux c" ;
printf("%s\n",p);
c语言对字符串常量的处理,在内存中开辟出一个字符数组来存储该字符串常量,并把开辟出的字符数组的首地址赋值给p。
注意:string[0] = 'a' 是可以的,而p[0] ='a' 是非法的,因为p指向的是字符串常量,常量的内容不可改变。把p指向另外一个字符串常量或字符数组是合法的。如:p ="hello world" ; p= string.
示例:在函数中实现字符串拷贝
#include<stdio.h> #include<stdlib.h> void copy_string1(char src[],char dst[]) /*copy_string1(char *src,char *dst)*/ { int i; for(i=0;src[i] != '\0';i++) dst[i]=src[i]; dst[i]='\0'; } void copy_string2(char *psrc,char *pdst) { for(;*psrc != '\0';psrc++,pdst++) *pdst=*psrc; *pdst='\0'; } int main() { char a[] = "linux c progam",b[20],c[20]; copy_string1(a,b); copy_string2(b,c); printf("%s\n%s\n%s\n",a,b,c); return 0; }
解析:1、copy_string1函数的参数是两个数组,而copy_string2函数的参数是两个指向字符串的指针。这两种方式是完全等价的。copy_string1(char src[],char dst[])等价于copy_string1(char *src,char *dst)
2、copy_string1(a,b),把a和b的值(该值是指向数组第一个元素的指针,而这个指针就是存放数组第一个元素的内存地址)赋给src和dst,src和dst保存的是a和b的首地址。copy_string2(z,c)也一样,
psrc和pdst也都保存这数组a和c的第一个元素地址。
3、对于copy_string1(a,b),a的值是a[0]即字符l内存地址,src的值也就是a的值,因此通过src可以很方便的对数组a进行操作,b也是一样的
注意:数组,如char a[20],a是指向数组第一个元素的指针,它的值不可以被该变,它在程序运行过程中始终指向数组的第一个元素。而在函数定义中,如copy_string1(char src[],char dst[]),src也是一个指针,它的值
是可以改变的,也就说它可以指向其他字符串指针中。
字符数组由若干个元素组成,每个元素存放一个字符。而字符串指针中存放的是地址(字符串的首地址),而不是将字符串存放在字符串指针中