一、前言:
1、指针就是地址,地址就是指针
2、指针是内存单元的编号,指针变量是存放地址的变量,还是有一点区别的
3、我们经常把指针变量说成指针,其实是有一点区别的。 指针是确定的地址编码,谁要地址编码干什么
二、指针基础知识
1、定义和使用
变量类型 * 变量名 : 比如 int *p 这个int * 就是指针类型,p就是变量名。
那么这个p就存储的是指针类型的数据,也就是地址。
那如何给这个变量p赋值呢?1,取其他变量的地址给他 2、将其他指针变量赋值给他
)、取地址
int a = 12
int *p = &a;
&是取地址符,取的是变量的地址。
2)取值
那如何获取指针变量p中的值呢?用取值符号 *
int c = *p; 就能把指针p指向的地址里面的存储的值取出来了。
3)改指针指向地址里面的值
*p = 30; 通过地址改变了地址里面存储的值了。
2、指针的大小 32位上是4,64位上是8, sizeof的结果
3、给指针随便乱赋值没有意义,就是野指针,因为他指向的地址是未知的额,操作地址内存区域旧货报错的。 比如 int *p=100,这就是乱赋值了。
4、空指针:指针没有指向任何内存:int *p = NULL; NULL是一个值为0的宏常量: #define NULL((void*)0)
5、万能指针:可以指向任意变量的内存空间:就是这个指针赋值的时候不会因为类型报错,他是包容任何类型
的指针的,否则把float数据的地址赋值给int *指针,操作会有错误的。
如果不知道数据是什么类型的,但是又要用到他们的指针,可以用这个万能指针。
只要在给指针赋值的时候强制转换一下(void *),然后在使用他的值的时候也强制转换一下就行了
int a =100;
void *p;
p = (void *)&a;
*(int *)p = 12;就行了
6、指针和数组
取数组的地址和取数组第一个元素的地址相同,也就是说数组地址存第一个元素,其他的依次排列
比如:第一个int元素地址是XXXXXXX0,那第二个int元素地址是XXXXXXX4,说明数组的中的元素都是紧挨着的
变量的地址是一个16进制 数,0-F组成的数组,一般是八位。
7、常量指针和指针常量:
常量指针(常指针):指向常量不可改变值的指针,指针可以重新赋值,但是指向的常量不可改变值。
const在 * 前面的 const int * p 或者int const *p (名字和用法有点出入,常量的指针给人的感觉是 * 在常量的前面,反过来记就行) ,可以以这么记:常量(const) 指针 (*)
7.1) 不能通过指针来改值,只能通过原来变量来改值
7.2) 可以给指针指向其他的变量的地址
指针常量:这个指针变量是常量的,指针不能重新赋值,但是指针指向的变量的值可以改变 const 在 * 后,在变量名前面的。int * const p(名字一样有出入,给热的感觉是const在 * 前面,反过来记就行 ) ,也可以这么记:指针(*) 常量(const) 7.3)可以通过指针来改变变量的值 7.4)不能给指针修改指向
8、数组和指针
数组名在编译的时候会被编译器编译成一个指针:用到数组的地方,编译器会隐式的转换成一个指针。除了两种情况不会:sizeof(数组名) 和 &数组名【取地址】,不会隐式转为指针。
因为数组一旦定义后,就不能再次重新指向新的数组数据,只能通过数组名改里面的值,所以本质上数组名是一个【指针常量】。
9、插入一条:指针的加减法运算:
计算机中,每个字节都有一个地址编码,一个指针就是指向这个字节的,%d能打印当前指针的值 ,而指针加1,不是给地址编码增加1,而是给地址编码增加 这个指针类型所占大小的数,比如int占4个字节,一个指针当前地址编码是XXXXXXX4,指针加1 变成了 XXXXXXX5,同样,减法也是一样的。当然如果是以16进制%p输出的话,可能读取的时候要注意下进制的转换。
指针加减法运算后可以直接赋值给同类型的指针变量就行,其实就是地址运算。
10、再次回到指针和数组
数组名这个指针是指向 数组第一个元素的首地址的,int a[3]
其中 a和&a和&a[0]的地址输出是一样的,但是他们又有所不同的:
输入的结果是%d:
a和&a和&a[0]的值是一样的
但是加法确不同:
a+1和&a+1和&a[0] +1 是不同的:a+1和a[0] +1 都是指向数组内下一个元素的首地址,但是&a+1却是指向数组末尾加1的地址。 所以他们的含义是不同的。
【注意:】但是奇怪的是如果把他们都赋值给一个指针变量 *p,然后p+1的结果又都是一样的 ,都是指向下一个元素首地址。
利用上面的特征,可以直接用数组名赋值的指针变量来操作数组元素了。 int *p = 数组名; p++,就指向下一个元素,然后操作就行了。
11、指针数组:一个数组里面都是指针 int *p[\3] 这种形式的。
一个数组大小是3,里面的数据类型是 int *指针类型的。
12、多级指针:**p:一级指针地址里面存的是另外一个指针的地址,需要两次才能访问到元素的值。
int a = 10;
int *p = &a;
int **q = &p;
访问:**q才是a的值
当然可以不用**,只用普通的 *q就行,但是这样可读性不好,不知道是指针的指针。
13、数组指针: 指向一个数组的指针,和上面的指针数组不是一回事,上面的着重点是数组,下面这个的着重点是指针。数组指针的地址虽然是数组的首地址,但是含义确实针对整个数组的,只要加一,就会跑到数组的末尾,所以上面的 &p应该是数组指针,应该用 int (*p)[3]来接收,才能解释为什么上面p+1和&a+1不是一个结果。
13.1插入说明: 我认为C语言不是强类型语言,对于类型不是很敏感,比如,基本类型之间,只要不是语法问题,可以随便相互赋值,char类型可以赋值给整型,整型可以赋值给char,long,short,浮点都是一样的,可以互相赋值,只不过的是乱赋值可能导致数据错误,不准确,不会编译错误。 数组可以赋值给整型,就是把数组首地址整型数据赋值给整型了。
同时:什么数值都可以赋值给指针变量,指针说白了就是地址的数字,整形赋值给指针,指针就指向这个数字编码的地址,比如 12赋值给任何指针,就表示指针指向内存中编码12的地址。。 当然很大可能是野指针。
再例如:上面的 int *p= &a; 虽然是把数组地址赋值给了指针,但是这个指针确不是指向整个数组的, 因为这个指针变量是 int *,他是整型指针,给他赋值,就是给他一个地址,而变量指的是一个征信的地址, 当计算的时候加1,就是指针指向的地址加上整型4个字节大小,就是指针地址加4,这也就解释了为什么这里的 p+1 和 &a+1结果是不一样的,因为&a取到的是数组指针,应该是 int(*p)[4],而不应该是int *p去接收。
13.2 ##################################C语言声明的优先级读法规则###################
读懂C语言的变量声明
----1)、声明是从变量的名字开始读取的,然后按照优先级依次读取
----2)、优先级从到到低:
--------2.1)、声明中被括号括起来的部分
--------2.2)、后缀操作:
------------圆括号()表示是一个函数
------------方括号[]表示是一个数组
------------前缀操作符: * 表示是一个指向。。。的指针
3)、如果const或者volatile紧跟着类型说明符,int等,他作用于类型说明符。其他情况下,const或者volatile作用于他左边紧邻的指针符号 *
##########################################################################
13.3、根据上面的规则就能区分指针数组和数组指针了:
指针数组: int *p[\3] :名字p右边是方括号,表示是一个数组,左边是:int *:表示数组的类型是
int *,也就是一组类型是int 指针的数组。所以上面的写法等价于: (int *)(p[\3])
数组指针: int (*p)[3] : 名称p被括号括起来,前面是 *说明是一个指针,括号右边是方括号,表示指针是指向一个数组的。
这里的声明的读法下一次将具体的阐述。
14、指针做函数参数:
像java中传递基本类型给函数无法修改外部数据,像修改外部基本数据的值,只能封装一层,但是C可以用指针做函数参数的方法来改变。
15、当数组名做函数参数的时候,可以写成int a[]做形参,也可以写成 *a的形式,在函数中使用是不影响的,可以用a[1] 的方式,也可以使用 *(a+1) 访问数组的元素。调用函数的时候只用传数组的名称就行 【需要注意的是:】函数体内无法根据形参计算数组的长度,需要在调用的时候先计算出来,无论那种方式都是一样的:sizeof(a) / sizeof(a[0]) 然后传给函数,因为形参只有首地址 。
16、指针作为函数的返回值 int *getA(){}这种形式,需要在函数体内返回指针。
17、指针和字符串
前面说过C语言中可以用字符数组存储字符串,初始化的时候可以用字符数组,也可以直接用字符串去
char a[] = {'a','b'} 或者 char a[] = "ab";
这种情况,可以用指针来操作每个字符的值。 char *p = a; p是指向第一个字符的首地址的额,然后利用指针的加减法运算都能操作每个字符。
第二种方式,直接把字符串赋值给一个指针变量: char *p = "12345";
这种方式可以重新给变量赋值新的字符串的值。 但是不能用指针改其中的字符,因为本质他不是字符数组
,字符串常量是存在静态区域的,常量是不能改的。
以上两种方式存储的字符串数据在底层存储方式是不一样的,所以一个能改字符,一个不能。 一个本质是数组,一个本质是常量。通过sizeof他们就能看出来,字符串常量的指针的长度是4,而数组的长度就是数组需要的长度 。
所以字符串常量的指针变量只能存常量的首地址
17、字符指针做函数的参数:
在基本数据类型中int等需要通过函数修改数据的值,可以传值的地址&a给函数,函数通过 *a接受,然后再函数中用 *a 来改值,但是字符串有些特殊
17.1字符数组形式的字符串,可以把数组名也可以吧数组整体地址&p传给函数,函数依然用 char *p 接受,无论传的是什么,接受到的都是首地址,并且是字符类型的,所以可以用上面的 *p的方式修改其中得字符
17.2 常量字符串形式的,需要传 &p,函数用 char **p接受,然后用 *p直接改字符串常量的值才行
【注意】;这里没有改原来字符串常量的值,而是给p重新指定了一个地址,相当于改了p的地址,所以需要用
**二级指针来接收,参数传指针的指针,本质是改了指针的地址值,原来的常量无法修改的。
18、const修饰符,参考第七点常量(的)指针和指针(的)常量
补充一点:如果const既修饰了* 也修饰了名称,那么指针的指向不能变,内存的值也不能变
19、应用场景:将指针数组作为main函数的参数:
int main(int grgs,char *args[]) 这样一来,grgs代表命令行参数的数量
args中每个指针元素代表每个参数的字符串数组