0.0补充关于指针
==指针P+1 = 指针P + sizeof(指针的类型) * 1== 加1,是在指针占用的内存空间里加1的。
(P+1)和 P+1的不同之处:
- *(p+1) 将指针变为了p+1,再取p+1的指针的值。
- *p + 1 取出指针p的值,然后再加1
1.指针
1.1.指针和指针变量
指针就是存放的地址
指针变量是存放的地址的那个值
看上图,首先 变量a是’F’,一个字符,是char类型的,存放的地址为10000,即pa为10000,char型4个字节,所以11000-11003代表的是所占的内存空间,是根据指针的数据类型,也可以说是pa存放的是a的地址
可以看出,a 的 地址(指针)pa 为10000 , pa 指向的单元的数据类型所占的空间为11000-11003
a —> ‘F’
pa —> 11000-11003
*pa —> ‘F’
不同的数据类型所占的内存空间不同,如果所占的内存空间有问题时,访问指针时就会出错
1.2.定义指针变量
指针变量中存放的地址指向的单元的数据类型
1.3.取地址运算符和取值运算符
int main()
{
char a = 'F' ;
int b = 123 ;
//这里是取址运算符
char *pa = &a ; //获取a的地址
int *pb = &b ; // 获取b的地址
// 不是通过变量a 和 b来访问,而是通过指针来间接的访问。
// 下面这里是取值运算符,也称为间接取值
printf("a = %c\n" , *pa) ; // 这里的 *代表的是取指运算符,不是指针了,取指针pa的值。
printf("b = %d\n",*pb) ; // 同理,*星号是取值
//通过指针来改变变量的值
*pa = 'C' ;
*pb += 1 ;
printf("now a = %c\n" , *pa) ;
printf("now b = %d\n",*pb) ;
}
}
printf("size of pa = %d\n",sizeof(pa)) ; // 字节
printf("size of pb = %d\n",sizeof(pb)) ;
printf("the add of a is = %p\n",pa) ; // %p用来输出指针类型自身的值
printf("the add of b is = %p\n",pb) ;
1.4避免访问未初始化的指针(野指针)
int *a ; // 指针的值是随机分配的,
*a = 123 ; //野指针
2.指针和数组
数组和指针关系密切,但数组绝不是指针,它们是哥俩好而已
scanf(“%d”,pa) ; //接收时不用取址操作符 & , 因为pa 是 a 的指针。
相同的还有字符串数组接收时也不用取址操作符。
案例1.1
int main()
{
int a ;
int *pa = &a ;
printf("请输入一个整数 : ") ;
scanf("%d",&a) ; // 变量直接的读取值
printf("a = %d\n",a) ;
printf("请重新输入一个整数: ") ;
scanf("%d",pa) ; // 这里是利用指针间接的读取值
printf("a = %d\n",pa) ; // 这个是求a的地址,pa的值,是以十进制的形式输出的 应用%p
printf("a = %d\n",*pa) ; // 取值,这个星号*是取值运算符
printf("a = %d\n",a) ; //取值,跟上面的一部是一样的,不过,这个是直接的取值
}
2.1数组名的真面目
数组名其实是数组第一个元素的地址
int main()
{
char str[128] ;
printf("请输入鱼c的地址域名:\n") ;
scanf("%s",str) ; // 这里数组读取数据时没有用 & 取址操作符。
printf("地址的域名为:%s " ,str) ;
printf("%p\n",str) ;
printf("%p",&str[0]) ; // char数组,每个元素都是一个字符,访问每个字符的地址,需要用到取址操作符
}
可以看到数组名的地址和数组第一个元素的地址相同
所以数组名的真实身份是数组的第一个元素的地址
int main()
{
char a[] = "FishC" ;
int b[5] = {1,2,3,4,5} ;
float c[5] = {1.1,2.2,3.3,4.4,5.5} ;
double d[5] = {1.1,2.2,3.3,4.4,5.5} ;
printf("a[0]--> %p , a[1] --> %p,a[2] --> %p , a[3] --> %p \n",&a[1],&a[2],&a[3],&a[4]) ;
printf("b[0]--> %p , b[1] --> %p,b[2] --> %p , b[3] --> %p \n",&b[1],&b[2],&b[3],&b[4]) ;
printf("c[0]--> %p , c[1] --> %p,c[2] --> %p , c[3] --> %p \n",&c[1],&c[2],&c[3],&c[4]) ;
printf("d[0]--> %p , d[1] --> %p,d[2] --> %p , d[3] --> %p \n",&d[1],&d[2],&d[3],&d[4]) ;
}
可以看出数组的地址,char 是一个字节 int float 是占用4个字节的,double是占用8个字节的,每一位的地址都按照相应的字节增加,
2.2指向数组的指针
如果用一个指针指向数组,应该怎么做?
只需要让指针 指向数组的第一个元素。
char a[] ...;
char *p ;
p = a ; // 语句1 指向的是数组的第一个元素,而不是数组
p = &a[0] ; // 语句2
语句1 和 语句2 是等价的,因为数组名等于数组的第一个元素。
2.3指针的运算(指针法)
当指针指向数组的元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后,第n个元素
对比标准的下标法访问数组元素,这种使用指针进行间接的访问元素的方法叫做指针法。
指针 p+1,不是单纯的加1,而是指针指向下一位元素,加一的背后是根据指针的数据类型而定的,char类型的指针加1是加4,
int,float,加4,double指针加一,背后加的是8,因为一个字节是8位。
char a[] = "FishC" ;
char *p = a ; // 指向的是数组的第一个元素,而不是数组
使用指针进行间接的访问元素。
printf("*p = %c , *p+1 = %c , *p+2 = %c\n",*p,*p+1,*p+2 ) ;
2.4指针和数组之间相互引用
int main()
{
int b[5] = {1,3,3,4,5} ;
printf("b = %d , *b+1 = %d , *b+2 = %d\n",*b,*b+1,*b+2 ) ;//这里取的并不是数组中的第二个的值,而是第一个元素增加1之后的值,如果想取数组中的值可以 *(b+1),这时取的才是数组中的第二个的值
printf("b = %p , *b+1 = %p , *b+2 = %p\n",b,b+1,b+2 ) ;
}
可以看到这里,并没有将数组b传递给指针,而是后面打印的时候,之间调用数组的指针,也是可以的。
int main()
{
char *str= "I love FishC.com" ; // 可以看到这里是一个字符指针变量,(算是野指针,只是用于举例)
int length ,i ;
length = strlen(str) ;
for(i = 0 ; i < length ; i++)
printf("%c",str[i]) ; // 然后,用下标访问指针的元素也是可以的,
printf("\n") ;
}
总结:
- 关于指针和数组,很像,但不一样
- 访问数组元素时,相互之间可以串着使用,
- 比如数组,用指针访问。
- 字符指针变量,用数组下标访问也可以。
2.5指针和数组名的左值右值关系
指针是左值,而数组名是一个地址常量,不是左值
下面举例,利用指针求字符串的字符个数
int main()
{
// 指针实现,字符串的长度,有多少个元素,不用函数,用指针来实现。
char str[] = "I love fishc.com" ;
int cnt = 0 ; // 字符的个数
while(*str++ != '\0') // 这里 *str,自增,相当于把字符串数组常量当做左值了,
{
cnt ++ ;
}
printf("%d",cnt) ;
}
//这样写是会报错的,报错信息如下,意思就是,左侧被赋值的数应该是一个变量,不能是字符串常量。
[Error] lvalue required as increment operand
正确代码
int main()
{
// 指针实现,字符串的长度,有多少个元素,不用函数,用指针来实现。
char str[] = "I love fishc.com" ;
char *target =str ; // 将数组名赋值给指针变量,
int cnt = 0 ; // 字符的个数
while(*target++ != '\0') // 这样左值自增,即指针变量自增,就没有错误了。
{
cnt ++ ;
}
printf("%d",cnt) ;
}
2.6 结论
数组名只是一个地址,而指针是一个左值。
3.指针数组和数组指针
指针数组是数组
数组指针是指针
3.1指针数组
指针数组是一个数组,每个数组元素存放一个指针变量
3.1.1指针数组的初始化
int main()
{
//指针数组的初始化
int a = 1 ;
int b = 2 ;
int c = 3 ;
int d = 4 ; // 这是5 个变量
int e = 5 ;
int *p1[5] = {&a,&b,&c,&d,&e} ; // 数组存放每个变量的指针
// 看结果
for(int i = 0 ; i < 5 ; i++)
printf("%d\n",*p1[i]) ; // 取值,最后输出1 2 3 4 5
// 这里如果不加星号,相当于取的是a,b等等的指针,并不是取的值。
}
3.1.2指针数组在字符串的应用
用指针数组来代替,字符串的二维数组
int main()
{
// char *sp = "fdjlf fdsf df" ; 下面的步骤相当于这一步,因为字符数组名可以被当做指针
char *p[4] = {
"nothing is impossible",
"just do it",
"believe yourselef",
"be better"
} ;
for(int i = 0 ; i < 4 ; i++)
printf("%s\n",p[i]) ; // 这里取值不加*,因为是取的整个字符串的地址,是要输出整个字符串
//如果这里取值 ,加*, *p[i] , 相当于取每个字符串的指针的字符了,
}
3.1.3单独举例来解释指针数组
int main()
{
char *sp = "my name is ljh" ; //这里的指针,相当于指针数组,因为,字符串是char类型的数组
for(int i = 0 ; i < 1 ; i++)
printf("%s\n",sp) ; //
}
char *sp = "my name is ljh" ; // 下面的步骤相当于这一步,因为字符数组名可以被当做指针
for(int i = 0 ; i < 14 ; i++)
printf("%c\n",sp[i]) ; //
char *sp = "my name is ljh" ; // 下面的步骤相当于这一步,因为字符数组名可以被当做指针
for(int i = 0 ; i < 5 ; i++)
printf("%c\n",*sp) ;
参考上面的三个例子
当定义了字符串的指针数组之后
如果要字符串,给出字符串的地址,即指针数组名,不需要加任何东西
如果要的是字符串中的每个字符,sp[i]
数组名相当于数组的第一个元素,*sp是取值运算符,取字符串的第一个值。m
不要把之前学的跟现在的弄混了!!!
==3.2数组指针==
数组指针是一个指针,它指向的是一个数组
3.2.1 初始化(二级指针)
int main()
{
int temp[5] = {1,2,3,4,5} ;
int (*p2)[5] = &temp ; // 因为temp表示的第一个元素的地址,&temp表示的数组的地址,所以数组的指针应该这样写。
int i ;
for(i = 0 ; i < 5 ;i++)
printf("%d\n",*(*p2+i)) ; // 是二级指针,先取内存空间的值,即指针,再取值。
printf("%p\n",*p2) ; // 是指向数组中第一个元素的指针 ,如果单单是p2的话,取出来的是指针的地址,但是这个体现不出来,要用因为数组名是指向第一个元素的指针。
printf("%p\n",*p2+1) ; // 指向数组中第二个元素的指针,这个只取p2+1就是指针的地址。
printf("%p\n",*p2+2) ; // 指向数组中第三个元素的指针
}
//前面要输出地址,只需要写指针名就好了,但是数组这里就不一样了原因如下。:
p2是数组的指针,指向这个数组,因为数组在某种意义上本就是指针,所以p2就指向指针所占的内存空间,p2的值,*p2才是数组的指针。下面二维数组也是同一个道理。
//解释*(*p2+i)
//temp数组本身就就是一个地址,&temp,相当于是数组temp【5】的元素地址的地址,而数组指针指向的是这个数组的本身,所以这个数组指针指向的内存空间中,含有元素的地址。所以打印时,要两次*取值。
- 可以参考1.1指针和指针变量的那个图
==4.指针和二维数组==
c语言中并没有真正的二维数组,而是以线性的方式扩展成二维数组
二维数组是一维数组的线性扩展,所以一维数组名看作指针,二维数组的数组名也看作指针
4.1 array表示的是什么
array是指向包含五个数组的元素的指针
int main()
{
int array[4][5] = {0} ;
printf("sizeof = %d\n",sizeof(int)) ;
printf("array 的地址 = %p\n",array) ;
printf("array + 1 的地址 = %p\n",array+1) ;
}
从输出结果可以看出,array是指向包含5个数组元素的指针
array 与 array+1 相差20,因为是16进制的,所以20/4=5 .相差5个元素
4.2 *(array+1) == array[1]
利用code证明上面的结果
array和array+1,array+1是在指针的地址所占用的内存空间上+1,是根据数据类型的字节数来加的,例如int就是4之类的。是二级指针。
int array[4][5] = {0} ;
int i , j , k = 0 ;
for(i = 0 ; i < 4 ; i++)
{
for(j = 0; j< 5 ; j++)
array[i][j] = k++ ;
}
printf("*(array+1) = %p\n",*(array+1)) ; //取值运算符,从指针所占空间地址取值,求的是结果是指针的值,也就是这个元素的地址。
printf("array[1] = %p\n",array[1]) ; // 等同于上面的写法
printf("array[1][0] = %p\n",&array[1][0]) ; //取址运算符,取出第二个数组中的第一个元素。
//结果相同
//同一个元素,不同的取值方法
printf("array[1][0] = %d\n",array[1][0]) ;
printf("array[1][0] = %d\n",*array[1]) ;
printf("array[1][0] = %d\n",*(*(array+1))) ;
4.3 ( (array+1)+3) == &array[1] [3]
array+1 = array + sizeof(指针的类型,字节数) * 1
==4.3.1 key==
指针p和下面的例子 array 是同一个道理,通过内存空间求地址的思想是一样的。
*(array+1)是第二行第一个元素的指针,然后这个指针 + 3
其实是 加上 3*4(字节数)的内存空间,得到的就是二行三列这个元素的地址(即指针)
地址与地址之间是可以通过相应的数据类型所占的内存空间相加得到的。
//同一个元素,不同的取址方法
printf("*(array+1)+3 = %p\n",*(array+1)+3 ) ;
printf("array[1][0] = %p\n",&array[1][3]) ;
//同一个元素,不同的取值方法
printf("array[1][3] = %d\n",array[1][3]) ;
printf("*(*(array+1)+3) = %d\n",*(*(array+1)+3)) ;
4.4结论
分别是一维数组,二维数组,三维数组,以及多维数组都是同一个道理。
==5.数组指针和二维数组==
int main()
{
int array[][3] = {{0,1,2},{3,4,5}} ;
int (*p)[3] = array ;
printf("*(*(p+1)) : %d\n",*(*(p+1))) ;
printf("*(*(array+1)): %d\n",*(*(array+1))) ;
printf("array[1][0]: %d\n",array[1][0]) ;
printf("================\n") ;
printf("*(*(p+1)+1) : %d\n",*(*(p+1)+2));
printf("*(*array+1)+1): %d\n",*(*(array+1)+2)) ;
printf("array[1][1]: %d\n",array[1][2]) ;
}
6.void指针和NUll指针
6.1void指针 (==使用时注意强制类型转换==)
int main()
{
int num = 1024 ;
int *pi = &num ;
char *ps = "FishC" ;
void *pv ;
pv = pi ;
printf("pi:%p,pv%p\n",pi,pv) ;
// printf("*pv:%d\n",*pv) ; //void类型的指针不要直接解引用,会报错
printf("*pv:%d\n",*(int *)pv) ; // 先将指针类型从void变为int,然后取值就可以了
pv = ps ;
printf("ps:%p,pv:%p\n",ps,pv) ;
// printf("*pv:%s/n",pv); 不推荐
printf("*pv:%s\n",(char *)pv) ;
//字符串类型的直接解引用不会报错是因为,指针指向字符串的起始地址
//然后一个字节一个字节的读下去,知道遇到\0停止,所以这里勉强可以打印出来
//但是这样写是不规范的 ,所以如果想要解引用,要强制类型转换,void换成对应的类型
}
6.2NUll指针
null指针是宏定义
int main()
{
int *p1 ;
int *p2 = NULL ;
printf("%d\n",*p1) ; // 这个再devc中也会有异常错误,没法运行出结果,事实上是会随机指向一个地址。
// printf("%d\n",*p2) ; //对null指针进行解引用,会报错,段错误
return 0 ;
}
7.指向指针的指针
int main()
{
int num = 520 ;
int *p = &num ;
int **pp = &p ;
printf("num: %d\n",num);
printf("*p: %d\n",*p) ;
printf("**pp: %d\n",**pp) ;
printf("&p: %p, pp: %p\n", &p,pp) ;
printf("&num: %p, p: %p, pp: %p\n",&num,p,*pp) ;
}
7.1指针数组和指向指针的指针
用案例来说明
int main()
{
char *cBooks[] =
{
"<c程序设计语言>",
"<c专家编程>",
"<c和指针>",
"<c陷阱与缺陷>",
"<c primer plus>",
"<带你学c带你飞>"
} ;
char **byFishC; //指向字符指针的指针的变量
char **loves[5] ; // []的优先级高,所以是一个数组,存放指向指针的指针
byFishC = &cBooks[5] ; //得到最后一本书的地址
loves[0] = &cBooks[0] ;
loves[1] = &cBooks[1] ;
loves[2] = &cBooks[2] ;
loves[3] = &cBooks[3] ;
loves[4] = &cBooks[4] ;
printf("小甲鱼出版的书有:%s\n",*byFishC) ;
printf("小甲鱼喜爱的书有: \n") ;
for(int i = 0 ; i < 5; i++)
{
printf("%s\n",*loves[i] ) ;
}
}
```
因为数组名就是指针的第一个元素,所以这里使用数组名的时候,只解引用了一次,就是代表指向指针的指针,不要弄混了。
```
7.2数组指针和二维数组
正常的案例
int main()
{
int array[10] = {0,2,11,3,4,5,6,7,8,9} ;
int *p = array ;
int i ;
for(i = 0 ; i < 10 ;i++)
{
printf("%d\n",*(p+i)) ;
}
}
数组指针访问二维数组,还是和之前一样的,一会百会。
int main()
{
int array[][4] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
int (*p)[4] = array ;
int i , j ;
for(i = 0 ; i < 3 ;i++)
{
for(j = 0 ; j < 4 ;j++)
{
printf("%d ", *(*(p+i)+j)) ;
}
printf("\n") ;
}
}
8.常量和指针
int main()
{
const float pi = 3.14 ; // 只读,不能被修改或者写入,是一个常量
printf("%f\n",pi) ;
}
8.1指向常量的指针
这就说明,不能通过指针修改常量的值
int main()
{
int num = 520 ;
const int cnum = 880 ;
const int *pc = &cnum ; //无法修改,是指向常量的指针
printf("cnum:%d, &cnum: %p\n",cnum, &cnum) ;
printf("*pc: %d, pc: %p \n", *pc, pc) ;
// *pc = 1024 ; // 修改指针的值,是不能的,会报错(通过解引用修改数据,不可以)
pc = & num ; // 指针可以修改为指向不同的常量
printf("*pc: %d, pc: %p \n", *pc, pc) ;
num = 1024 ; // 可以通过修改num的值,来改变指针指向的值
printf("*pc: %d, pc: %p \n", *pc, pc) ;
}
8.2常量指针
刚刚我们说指针不能通过解引用修改指针指向的值,但是可以修改指针的指向,如果想让指针的指向也不能修改,可以设为常量指针。
int main()
{
int num = 520 ;
const int cnum = 880 ;
int * const p = &num ; // 常量指针, 指针本身不可以改变,但是指向的值是可以改变的
*p = 1024 ; //常量指针的值是可以改变的
printf("p: %d\n",*p) ;
// p = &cnum ; //报错 ,指针自身是不可以被修改的
printf("*p: %d\n", *p) ;
}
const int * const p = &num ; // 指向常量的常量指针 ,都不可以修改。
8.3指向 “指向常量的常量指针” 的指针
const int * const p = &num
//指向常量的常量指针
const int * const *pp = &p
指向 “指向常量的常量指针”的指针
int main()
{
int num = 520 ;
const int cnum = 880 ;
const int * const p = &num ; // 指向常量的常量指针
const int * const *pp = &p ; // 指向 “指向常量的常量指针”的指针
printf("pp: %p , &p: %p \n",pp,&p) ;
printf("*pp: %p, p: %p , &num: %p\n",*pp,p,&num) ; // 一层解引用
printf("**pp: %d, *p: %d , num: %d \n",**pp,*p,num) ; //二层解引用,取值了。
}