目录
一、引入
scanf("%d",&a);
这个语句,大家一定不会陌生,那么&这个符号到底是什么意思呢?
&就是取地址的意思。即先拿出a的地址,再将输入的值放在a的地址上,因此a就有了值。
存放值的容器是变量,而存放变量的容器就是指针。
二、指针
1.输出
printf("%p",&a);
指针的输出采用%p的形式,输出的值为0x开头的十六进制。
问题:既然地址是十六进制,那为什么不用int型变量来存放这段值,而是用指针?
int main(void)
{
int a = 10;
printf("%p\n",&a);
int b = &a;
printf("0x%x",b);
}
输出结果如下:
0x7ffd7c608304
0x7c608304
注:不同计算机,每一次所取的地址是不相同。
由上可见,后方段虽相同,但前面是不同的,故不可用int来存放地址。(好像是由操作系统的位数决定,x32好像没问题,x64就有问题)
2.大小
地址的大小由操作系统位数决定,x32是4字节,x64是8字节。
3.取地址要求
&后方可以接变量,接数组等等,但不可以接表达式。
如
printf("%p",&a++);
printf("%p",&(a+b));
printf("%p",a);
printf("%p",&++a);
printf("%p",array[1]);
这些都是错误的写法。
4.相邻定义的变量地址关系
int main(void)
{
int a = 10;
int b = 12;
printf("%p\n",&a);
printf("%p\n",&b);
}
输出结果如下:
0x7ffc83b19de0
0x7ffc83b19de4
可见连续定义的变量,它们的指针也是连续的,从小到大的,而且两者的差值就是他们类型大小。
三、函数
1.指针可以做为输入参数,也可以作为返回参数。
int *p func(int * q);指输入一个int型指针,返回一个int型指针。
2.指针输入与变量输入的差异
提前说明:变量作为参数输入,函数内部对其修改,不会影响外界变量。
指针作为参数输入,函数内部对其修改(非地址修改),会影响外界指针所指的变量。
如为地址修改,内外指针将无关系。
(1)变量原理:(单指main的变量)变量输入时,void func(int a);它会进行一个赋值a(内界) =a(外界)。而内界的变量a的生存期与作用域存在于自定义函数的块内,而外界的变量a存在于main函数的块内。两者是互不联系的。
如:
void func(int a)
{
内界变量a的生存期与作用域
}
void main(void)
{
外界变量a的生存期与作用域
}
由此可见,main与自定义函数的块并没有交集,即内外变量a是没有任何关系的,单单只有输入时,将外界a的值给了内界a。
(2)指针原理:
前提理解:我将赋值方式分为两种,一种是直接赋值如int a = 1;另一种叫做地址赋值,如:
int a = 1;
int *p = &a;
*p = 2;
两者区别就是,直接对变量进行处理,还是通过指向变量的指针进行处理(多了中间体)。
注:array[1] = 10;属于直接修改。
解释1:
void func(int *p);指针是地址,传入自定义函数的,自然就是地址。因此在自定义函数内进行的是地址赋值,而不是直接赋值。那么原理就明显了。
在自定义函数内,我们通过指针p这个中间体,来对外界的变量a进行了修改,即进行了地址赋值。与变量输入相比,指针输入,多了一条一直联系着自定义函数与main函数的桥梁——指针地址。
解释2:
这也很好的说明了,为什么在自定义函数内,修改输入指针的地址,将会导致内外指针无关系。
void func(int *p)
{
int b = 10;
p = &b;
}
此时,指针p不在指向外界的变量a,即连接内外函数的桥梁没了。故对指针的修改,将不会影响外界的a。
3.返回指针
(1)输入指针返回指针(最安全的方法)
int * func(int *p)
{
return *p;
}
注意:1.输入的指针要提前在main中定义好内存。
2.在自定义函数内部,不要修改输入指针的地址。
3.输入的指针与返回的指针是同一个指针。
(2)用二级指针(可修改地址)
前提:
二级指针,即存放一级指针的地址,记为int **p。
int a = 10;
int *p = &a;
int **q = &p;
解释:
void func(int**q)
此时,要将二级指针看作包裹物,这个包裹物在外界,因此当一级指针改变时,仍然通过这个二级指针找到已经修改的一级指针。
注意:自定义函数对一级指针的修改,得是malloc。自定义函数结束后,会对内存进行清理,而malloc的内存得由free来清理。
(3)用结构体(可修改地址)
typedef struct _node {
int *p;
}Node;
void func(Node p);
作用原理与二级指针相似。
总结
(1)输入一级指针返回一级指针
(2)二级指针,结构体,无需返回值
(3)一定要malloc内存给二级指针与结构体,让其能够存放一级指针
补充:返回指针,可以返回输入的指针,可以是自定义函数内malloc的指针,可以是全局变量或者静态本地变量。
(之后简洁表达,即以笔记表达,不在解释)
四、数组与指针关系
单字符修改:array[0] = '1'; 多字符修改:p = "125456";
单字符赋值:array[0] = array[1]; 多字符赋值:p = q(两个都是指针)
1.数组,可以单字符修改,不可以多字符修改 //都只改变值
可以单字符赋值,不可以多字符赋值
2.指针,不可以单字符修改,但可以多字符修改 //创建新地址
不可以单字符赋值,但可以多字符赋值 //指向新地址
3.一指针一数组
(1)将数组赋值给指针,若进行数组操作,只改值,若进行指针操作,则改地址
(2)指针进行malloc也是同理。
4.理解常量,常量指针,指针常量。
常量:const int a;--->不可以进行直接修改
常量指针: int *const p; --->不可以对地址进行修改
指针常量: const int * p ; ---->不可以进行地址修改//不是改地址,是指上方我讲的地址修改
//以*为分界线,区分常量指针与指针常量
5.数组就是常量指针,即不可以更改地址,故赋值不可行。
6.指针定义时,最好定义成NULL的形式,不仅可以防止free错误,也可以告诉自己,这个指针没有地址。(指针定义时,如int * a;这样是没有内存的,此时给它个内存,可以看作一个可以改变地址的数组)
7.指针不可以集成初始化,数组可以。
五、一些杂碎
1.结构指针只能进行多字符赋值 p = q(两个都是结构指针)
2.在main中数组sizeof是数组的元素个数,而指针则是指针的大小,同时,发现在自定义函数内sizeof数组,它的值是地址大小,故可以知道数组输入时,是以地址输入,说明数组名就是指针。
如array[0] = p[0] = *array = *p;
3.用自定义函数使指针变为常量指针,指针常量(打破重复定义的局限)
void func(const int *a){}
void func(int *const a){}
得用{},表明是定义,不是声明。
六、指针运算
1.指针能够进行运算如p++,*p++等操作,这是在改变指针的指向。
指针的名字,默认是字符串的一个字符的地址,故*p返回第一个地址,现进行p+=1;则可以让*p返回第二个字符(+1指加上一单位的指针所指类型大小)。因此有*p++这个运算,此时++优先级高于*,即先++,再返回当前p的字符(似乎*p++是很底层的东西,运行速度会更快)。
*p++常用于指针遍历,p[count++]也可以。但数组遍历只能用array[count++],为什么指针加一这种行为,其实改变了地址,而数组地址不可以改变。
2.指针能够相减,不能相加与乘除。
指针相减得到的是,两个指针所指元素的下标的差值。
注:只能用于指针所指的类型一致。
七、强制类型转换
变量之间赋值,会自动进行强制类型转换,指针之间赋值并不会自动进行。
指针最好不要用强制类型转换,或者说无论是指针还是变量都少用为好。
int * a = (int *) p;
即将指针p强制转换为int *类型,并将地址拿给指针a。
注:此时a,b指向同地址,a改变,即b改变。
八、动态内存分配
1.void * malloc(int init_size) :创造init_size的内存,并以void*形式返回。
2.void * realloc(const int *p,init_more ) :在p指针的地址上在增加init_more个字节,并以void*返回
注:realloc的地址开头是p的地址开头,所以不要想着free(p)或者随意扩大数组。
想要扩大数组:可用可变数组或者链表。可变数组存在内存占用问题与每次扩大都要复制问题。
3.void * calloc(int n,init_size) :(n指元素个数,init_size指单个元素的大小)与malloc相同,只是这个会将所有元素集成初始化为0。
注:输入的都是字节,最好用sizeof()*n的形式。
注注:一定要free(原地址)!!!
当内存不足时,将会返回NULL;
九、指针作用
1.使自定义函数有修改外界变量的能力。
2.输入大类型的数据,如数组,结构体。
3.用于返回多个值,即返回数组指针。
4.动态内存分配.
5.前向声明(利用指针大小确定,来进行定义)
6.链表
.............
十、二维数组、字符串数组、二级指针
二维数组int array[100][100];
字符串数组 int *array[100];
二级指针 int **p;
其实这三个本质上是相同的。
1.二维数组是每一位存一个字符,定义的时候就明确了每一行能存的位数。一整行可以看作一个常量指针。
2.字符串数组,定义时定义了指针个数,但并没有明确的内存,故给每个指针malloc内存后,作用与二维数组相同。
3.二级指针,它本来只能存放一个地址,但malloc后,不久类似字符串数组了吗,再按字符串数组的操作继续malloc,就可以当作字符串数组了。
注:二维数组本质上是一级指针,不是二级指针
原因:
void func(int **p);
void func(int (*p)[10]);
二维数组不能用第一条输入,第二条可以。第二条指一个指向10int型的数组的一级指针。
由二维数组元素排序的内存地址的连续性,也可以解释。
十一、问题总结
问题1:为什么array[1] 与 1[array]作用效果相同?
array数组名就是地址,为0,array[1]即指返回(0+1)的地址。而1[array] 也可以表示为返回(1+0)的地址,因此作用效果相同。
问题2:if/else/while 内部的本地变量能否通过指针形式返回?
不可以,系统会清理内存,清理指针,除非静态本地变量。
问题3:定义一个数组,然后用指针指向数组的最后一位,再进for循环,依次给数组后面的值赋值,而扩大数组可以吗?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int shuzu[2] ={0};
int t = 2;
for (;t<=10;t++) {
int *p =(int *)malloc(4);
p = &shuzu[2];
*p = t;
}
//遍历
int i = 0;
for (;i<11;i++) {
printf("%d\n",shuzu[i]);
}
}
答案是不可以的,因为malloc的内存,只存在那次for循环里,结束那次之后就被清理掉了。
而上方所讲自定义函数可以返回malloc的内存,是因为无论有无输入参数,会发现最后都有一个在外界的指针来指向这个malloc的地址,因此才能存在。