指针的学习笔记

1.指针的大小

             一个基本的数据类型(包括自定义类型)加上“*”号就构成了一个指针类型的变量。这个变量的大小是一定的,与“*”号前面的数据类型无关。“*”号前面的数据类型只是说明指针所指向的内存里存储的数据类型。
因此,在32位系统下,不管是什么类型的指针类型,其大小都是4byte。可以使用sizeof(void *)进行测试。
      也可以这么理解,因为“*”存放的是地址,因此“*”变量的大小,跟系统存储的地址的类型的值大小是一致的。
注意:sizeof是关键字不是函数,函数求值是在运行的时候,而关键字sizeof求值是在编译的时候。

2.指针的初始化

指针的初始化,这个问题可以看看下面的这个示例。
int *p = NULL;
//-----------------------
int *pp;
*pp = NULL;
对于变量p以及pp的操作有什么区别?

2.1 变量p

对于int *p=NULL;
1.定义了一个指针变量p,这个指针变量指向的数据类型为int;
2.在定义变量p的同时,将p的值设置为NULL;
3.注意:此时只是指针p的值为NULL,这就是初始化的过程。但是*p没有赋值。

2.2变量pp

1.int *pp;定义了一个指针变量pp,这个指针变量指向的数据类型为int;
2.pp本身的值是多少不得而知,pp有可能保存的是非法地址,那么*pp赋值为NULL,将是访问非法地址。
3.注意:在定义指针变量的同时,应该给指针赋初值,如2.1

3.指针与数组

指针就是指针,指针在32位系统下,永远是4个byte,其值为某一内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须制定其元素的类型和个数。数组可以存放任何类型的数据,但不能存函数。

3.1左值与右值

      出现在赋值符“=”右边的就是右值,出现在其左边的就是左值,如x=y。
左值:这个上下文环境中,编译器认为x的含义是x所代表的地址。这个地址只有编译器知道,在编译的时候确定,编译器在一个特定的区域保存这个地址。
右值:这个在上下文环境中,编译器认为y的含义是y所代表的地址里面的内容。这个内容是什么,只有到运行时才知道。
出现在赋值符左边的符号所代表的地址上的内容一定是可以被修改的。也就是只能给非只读变量赋值。

3.2数组作为左值与右值

当数组作为右值的时候,代表的是数组首元素的首地址,相当于a == &a[0]。不是数组的首地址。而数组名字不能作为左值,编译器会认为数组名作为左值代表的意思是数组的首元素的首地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素而无法把数组当一个总体进行访问。

3.3 访问方法

访问方法分为以指针的形式访问和以下标的形式访问。
char * str = "abcdefghij";//A
char str1[] = "1234567890"; //B

3.3.1访问指针

定义了指针变量str,str本身在栈上占4个byte,str里面存储的是一块内存的首地址。这块首地址存在静态区,其空间是10byte,这块内存没有名字。对这块内存的访问完全是匿名访问。要取出‘b’,使用指针和下标两种办法。

1.使用数组str[1]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。str[1]
,被解析成:先取出str里存储的地址值,然后加上中括号中1个元素的偏移量,计算出新的地址,然后从新的地址中取值。

2.*(str+1)先取出str里存储的地址值,然后在加上1个字符的偏移量,得到新的地址值,然后取出新的地址值上的值
以下标的形式访问在本质上与以指针的形式访问没有什么区别,只是写法上不同而已。

3.3.2访问数组

str1数组有10个char类型的元素,其空间大小为10.数组str1本身在栈上面。访问数组str1,必须先根据数组的名字找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的“具名+匿名”访问

3.3.3 两者的关联

相同点:
指针和数组都可以使用指针和下标的形式访问
不同点:
指针是完全的匿名访问
数组是具名+匿名访问
注意:偏移量的单位是元素的个数而不是byte数,在计算新地址的时候千万别弄错了。

3.4 a 与&a

	int a[10] = {5, 6, 7, 8, 9};
	int *ptr = (int *)(&a + 1);
	printf("%d, %d\n", *(a+1), *(ptr-1));
对指针类型加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1.所以,一个类型为T的指针的移动,以sizeof(T)为移动单位。
a是一个一维数组,有10个元素,ptr是一个int型的指针。
&a + 1,取数组a的首地址,该地址的值加上sizeof(a)的值,也就是&a+10*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。
第二个语句,则是把上一步计算出来的地址,强制转换为int *类型,赋值给ptr
*(a+1),a,&a的值是一样的,但是意思不一样,a是数组首元素的首地址,也就是a[0]的首地址,&a是数组的首地址,a+1是数组下一元素的首地址,也就是a[1]的首地址
*(ptr-1),因为ptr是指向a[10],并且ptr是int *类型,所以*(ptr-1)指向了a[9],a[9]没有赋值,所以输出的是0.

3.5指针和数组的对比

指针数组
保存数据的地址,任何存入指针变量p的数据都会被当作地址来处理。
p本身的地址由编译器另外存储,存储在哪里,我们并不知道
保存数据,数组名a代表的是数组首元素的首地址而不是数组的首地址。
&a才是整个数组的首地址。a本身的地址由编译器另外存储
简接访问数据,首先取得指针变量p的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。
指针可以以指针的形式访问*(p+1);也可以以下标的形式访问p[i]。
但是其本质都是先取p的内容然后加上i*sizeof(类型)个byte作为数据的真正地址。
直接访问,数组名a是整个数组的名字,数组内每个元素并没有名字。
只能通过“具名+匿名”的方式访问其某个元素,不能把数组当一个整体来进行读写操作。
数组可以以指针的形式访问*(a+1);也可以通过下标的形式访问。但其本质都是a所代表的数组首元素加上i*sizeof(类型)个byte作为数据的真正地址
通常用于动态数据结构通常用于存储固定数目且数据类型相同的元素
相关的函数为malloc和free隐式分配和删除
通常指向匿名数据(当然也可指向具名数据)自身即为数组名
注意:一个类型为T的指针的移动,以sizeof(T)为移动单位。特别注意的是数组和结构体。比如,3.4中所示。
typedef struct st_type{
	int i;
	char *str;
	float j;
	long k;
}type_a;	
type_a * p = NULL;
	printf("sizeof(type_a):%ld\n", sizeof(type_a));
	printf("p:%d, p+1:%d\n", p, p + 1);
因为p已经设置为NULL了,所以p+1直接就是0+sizeof(type_a)(注意存放数据时的地址对齐问题)

3.6 数组指针与指针数组

int *p1[10];//A
int (*p2)[10];//B
优先级情况:(),[]这两个都属于同一级别(1级结合方向是从左到右),*属于2级,比()以及[]的优先级别都低
A情况,p1先与“[]”结合,构成一个数组的定义,数组名为p1,int*修饰的是数组的内容,即数组的每个元素。因此A是一个数组,其包含10个指向int类型数据的指针,也就是指针数组。
B情况,括号里的p2先使用*进行修饰,成为一个指针,指针的变量名为p2,int修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是一个匿名的数组。因为p2是一个指针,它指向一个包含了10个int类型数据的数组,即数组指针。

3.7 数组作为函数参数

C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。同样,函数的返回值也不能是一个数组,而只能是指针。函数本身是没有类型的,只有函数的返回值才有类型。

3.8一级指针参数

能够把指针变量本身传递给一个函数?
void fun(char *p)
{
	char c = p[3];
	char d = *(p+3);
}

int main()
{
	char *p2 = "abcde";
	fun(p2);
	return 0;
}
p2是函数main里面的一个局部变量,它只在main函数内部有效。既然它是局部变量,fun函数肯定无法使用p2的真身。哪函数调用怎么办?其实是p2的一个拷贝,假设为_p2,因此传递到函数内部的是_p2,而非p2本身。
如何解决这个问题?
void get_memory(char *p, int num)
{
	p = malloc(num*sizeof(char));
}
int main(void)
{
	char *str = NULL;
	get_memory(str, 10);
	strcpy(str, "hello");
	free(str);//free并没有起作用,内存泄露
	return 0
}
malloc的内存地址并没有赋值给str,而是赋给了_str。而这个_str是编译器自动分配和回收的,我们根本就无法使用。
方法一:用return
char * get_memory(char *p, int num)
{
	p = malloc(num*sizeof(char));
	return p;
}
int main(void)
{
	char *str = NULL;
	str = get_memory(str, 10);
	strcpy(str, "hello");
	free(str);
	return 0
}
方法二:用二级指针
void get_memory(char **p, int num)
{
	*p = malloc(num*sizeof(char));

}
int main(void)
{
	char *str = NULL;
	get_memory(&str, 10);
	strcpy(str, "hello");
	free(str);
	return 0
}
注意:这里的参数是&str而非str。这样传递过去的是str的地址,是一个值。在函数内部,用“*”开锁;*(&str),其值就是str。所以malloc分配的内存地址是真正赋值给了str本身。

4.函数指针

函数指针,就是函数的指针,它是一个指针,指向一个函数。
void fun()
{
	printf("this is func()\n");
}

int main()
{
	void (*p)();
	*(int*)&p = (int)fun;
	(*p)();
	return 0
}
void (*p)();定义了一个指针变量p,p指向一个函数,这个函数的参数和返回值都是void。
&p是求指针变量p本身的地址
(int*)&p表示将地址强制转换成指向int类型数据的指针
(int)fun,表示将函数的入口地址强制转换成int类型的数据
*(int *)&p = (int)fun;就是表示将函数的入口地址赋值给指针变量p。(这句等效于p = fun)
(*p)();就是函数调用。

关于函数指针:
1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
2)调用函数实际上等同于“跳转指令+参数传递处理+回归位置入栈”,本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器
3)函数调用的本质是跳转到某一个地址单元的code去执行,所以可以“调用”一个根本不存在的函数实体


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值