AL控制组——C语言指针笔记

目录

一、引入

二、指针

1.输出

2.大小

3.取地址要求

4.相邻定义的变量地址关系

三、函数

1.指针可以做为输入参数,也可以作为返回参数。

2.指针输入与变量输入的差异

3.返回指针

四、数组与指针关系

五、一些杂碎

六、指针运算

七、强制类型转换

八、动态内存分配

九、指针作用

十、二维数组、字符串数组、二级指针

十一、问题总结

一、引入

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的地址,因此才能存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值