C语言学习笔记---指针初阶

C语言指针初阶

前言:
什么是指针?
指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。
由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”
即:能通过它能找到以它为地址的内存单元
另外,平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

/知识点汇总/

1、指针

指针就是地址,口语中说的指针指的是指针变量
单元编号 = 地址 = 指针,相互等价。

指针变量
由&(取地址操作符)取出变量的内存起始地址,把地址可以存放在一个变量这个变量就是指针变量。

指针变量,用来存放地址的变量(存放在指针中的值都会被当作地址处理)

1.1、指针的概念例程

/*例程1.1*/
#include <stdio.h>
int main()
{
	int a = 10;//给变量a分配4个字节
	int* pa = &a;//拿到的是a的4个字节中第一个字节的地址
	//pa为int类型的指针变量
	*pa = 20;//解引用
	return 0;
}
#include <stdio.h>
int main()
{
	int a = 10;//是向内存中的栈区空间申请4个字节的空间,这4个字节用来存放10这个数值

	int* pa = &a;//取得的是首字节的地址,存放在pa变量中,pa就是指针变量

	pa = 0x00112233;//将一个值,赋给pa指针变量,那么这个值就会被当作地址处理

	return 0;
}

注意:指针的大小在32位平台是4个字节,在64位平台是8个字节

一个内存单元的大小是一个字节
内存中如何编址?0/1
32位计算机 — 32根地址线 ---- 2^32 — 4GB
64位计算机 — 64根地址线 ---- 2^64 — 8GB

1.2、指针的大小例程

/*例程1.2*/
#include <stdio.h>
int main()
{
	int* pa;
	char* pc;
	float* pf;
	printf("%d\n",sizeof(pa));//4
	printf("%d\n",sizeof(pc));//4
	printf("%d\n",sizeof(pf));//4
	return 0;
}

小结
内存被划分为一个个的内存单元,每个内存单元,每个内存单元的大小是一个字节
每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中称为指针
地址要存储的话,存放在指针变量中
每个内存单元都有唯一的地址来标识
在32位机器上地址的大小是4个字节,所以指针变量的大小也是4个字节
同理:64位机器上地址的大小是8个字节,所以指针变量的大小也是8个字节

#include <stdio.h>
int main()
{
	printf("%d\n", sizeof(char*));//4
	printf("%d\n", sizeof(short*));//4
	printf("%d\n", sizeof(int*));//4
	printf("%d\n", sizeof(float*));//4
	printf("%d\n", sizeof(double*));//4
	return 0;
}

2、指针解引用操作和指针类型的意义

(1)指针类型决定了,指针解引用的权限有多大
(2)指针类型决定了,指针的移动步长,能走多少个字节

2.1、指针解引用操作和指针类型的意义例程1

/*例程2.1*/
#include <stdio.h>
//int main()
//{
//	int a = 0x11223344;
//	//int* pa = &a;
//	//*pa = 0;
//	//指针类型决定了:指针解引用的权限有多大
//	char* pc = &a;
//	*pc = 0;
//	return 0;
//}
int main()
{
	int arr[10] = { 0 };
	char* p = arr;
	int*  pc = arr;
	//数组名取地址均是元素的首地址
	printf("%p\n",p);
	printf("%p\n", p+1);//char+1
	printf("%p\n", pc);
	printf("%p\n", pc+1);//int+4
	return 0;
}

2.2、指针解引用操作和指针类型的意义例程2

/*例程2.2*/
#include <stdio.h>
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d",arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr,sz);
	int* p = arr;//以元素的形式操作
	//char* pc = arr;//一个一个字节的操作
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = 1;
	}
	print(arr,sz);
	return 0;
}
#include <stdio.h>
int main()
{
	int arr[5] = { 0x11223344,0x11223344, 0x11223344, 0x11223344, 0x11223344};

	int* p = arr;
	//char* p = arr;
	//short* p = arr;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = 0;
		p++;//根据指针的类型决定,走一步的大小
	}

	return 0;
}

小结
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如:char类型指针解引用就只能访问一个字节,而int类型的指针的解引用就能访问四个字节*

2.3、指针解引用操作和指针类型的意义例程3

#include <stdio.h>
int main()
{
	int a = 0x11223344;
	int* p = &a;//int* 决定了访问4个字节
	*p = 0;

	char* p = &a;//char* 决定了访问了1个字节
	*p = 0;

	short* p = &a;//short* 决定了访问了2个字节
	*p = 0;
	return 0;
}

2.4、指针解引用操作和指针类型的意义例程4

#include <stdio.h>
int main()
{
	int a = 0;

	int* pa = &a;

	char* pc = &a;

	//printf("pa = %p\n", pa);
	//printf("pc = %p\n", pc);
	//地址打印出来相同,因为指针类型大小相同,char也能够存储int a变量

	printf("pa = %p\n", pa);
	printf("pa+1 = %p\n", pa+1);
	//int* 指针加1,对应地址加4个字节
	
	printf("pc = %p\n", pc);
	printf("pc+1 = %p\n", pc+1);
	//char* 指针加1,对应地址加1个字节
	return 0;
}

小结
指针类型是有意义的:指针类型决定了指针+1/-1,跳过几个字节
int* 的指针+1,跳过4个字节
char* 的指针+1,跳过1个字节
short* 的指针+1,跳过2个字节
double* 的指针+1,跳过8个字节
:指针类型决定了指针向前走或向后走一步的大小/长度
也决定了,对指针解引用时有多大的权限(能够操作几个字节)

3、野指针

野指针:就是指针指向的位置是不可而知的(随机、不确定、没有明确限制的)
野指针成因
a、指针未初始化
b、指针越界访问
c、指针指向的空间释放,即置NULL

3.1、指针未初始化例程

/*例程3.1*/
#include <stdio.h>
int main()
{
	int* p;//p为局部的指针变量,局部变量不初始化的话,默认为随机值
	*p = 20;//对随机值解引用就是非法访问内存
	//所以这里的p为野指针
	return 0;
}

3.2、指针越界访问例程

#include <stdio.h>
int main()
{
	int* p = (int*)0x11223344;
	*p;//越界,不可随机的,随意捏造的指针
	return 0;
}
/*例程3.2*/
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 10; i++)//i<=10会导致p++移动地址范围超出
	{
		*p = i;
		p++;
	}
	return 0;
}

3.3、指针指向的空间释放例程

/*例程3.3*/
#include <stdio.h>
int* test()
{
	int a = 10;//a局部变量
	return &a;//a局部变量的地址会改变,临时创建a也是临时存储的内存地址
}
int main()
{
	int* p = test();//每次调用test()a的地址会改变
	*p = 20;//解引用的地址不是以前的地址了,非法访问
	return 0;
}

小结
如何避免野指针的提高规范性?
a、变量和指针变量最好都初始化,如果明确知道指针应该指向哪里,就初识化正确的地址;
当不确定初始化的值时,可以初始化为NULL(0)
如:int*p = NULL;
b、小心指针越界,明确范围避免越界(C语言本身也并不会检测数组是否越界)
c、指针指向空间释放,及时置为空NULL(0作为地址时,用户程序是无法访问的)避免非法访问
d、避免返回值局部变量的地址
e、指针使用之前检查其有效性

3.4、使用指针之前检查有效性例程

/*例程3.4*/
#include <stdio.h>
int main()
{
	int* p = NULL;
	*p = 10;//报错,空指针的0地址不属于操作系统的
	//判断一下指针有效性
	if (p != NULL)//不为空指针那么就是有效地址
	{
		*p = 10;
	}
	return 0;
}

4、指针的运算

(1)、指针 + - 整数(偏移量)
(2)、指针 - 指针
(3)、指针的关系运算(指针的大小比较)

4.1、指针的+ - 整数例程

/*例程4.1*/
#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//使用指针打印数组的内容
	int i = 0;
	int* p = arr;
	//p --> arr //指向首元素的地址
	//arr == p 等价
	//arr + i  == p + i 
	//*(arr + i) = *(p + i) //本质首元素地址加偏移量
	//又因为*(arr + i)依次偏移指向每一个元素等价于 ,arr[i]遍历每个元素
	//那么*(arr + i ) == arr[i] == *(p + i )
	//了解,指针加法交换律应用:
	// *(arr + i) == arr[i]
	// *(i + arr) == i[arr]

	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
		//printf("%d ", *(arr + i));
		//printf("%d ", arr[i]);
		//printf("%d ", i[arr]);
		
		//p指向数组首元素的地址
		//p + i 是数组中下标为i的元素地址
		//p + i 起始时跳过了 i*sizeof(int)个字节
	}
	return 0;
}

4.2、指针 - 指针例程

/*例程4.2*/
#include <stdio.h>
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	printf("%d\n",&arr[9] - &arr[0]);//9
	//指针-指针得到的是两个指针之间的元素个数
	return 0;
}

小结
指针-指针的前提
两个指针指向同一块区域,且指针类型也相同
指针–指针得到差值的绝对值,是指针和指针之间的元素个数

4.3、指针的关系运算(指针的大小比较)例程

写法一

#include <stdio.h>
#define N_VALUES 5

int main()
{
	float values[N_VALUES];
	float* vp;

	for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		*--vp = 0;
	}
	return 0;
}

写法二

#include <stdio.h>
#define N_VALUES 5

int main()
{
	float values[N_VALUES];
	float* vp;

	for (vp = &values[N_VALUES]; vp >= &values[0];vp--)//看似简化,方便理解,但是C语言标准不允许/不建议
	{
		*vp = 0;
	}
	return 0;
}

注意区分识别两种写法
a、省略for判断条件的写法

for (vp = &values[N_VALUES]; vp > &values[0];)
{
	*--vp = 0;
}

b、正常逻辑的写法

for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
{
	*vp = 0;
}

注意:虽然大部分编译器支持第二种写法,但应该避免这种地址超出首地址的写法,因为标准并不保证它可行
标准规定
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较
但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

5、指针求字符串长度运算 — 模拟strlen函数

5.1、计数器写法

/*例程5.1*/
#include <stdio.h>
//计数器写法
int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	//strlen()---求字符串长度
	int len = my_strlen("abc");
	printf("%d\n",len);
	return 0;
}

5.2、递归写法

#include <stdio.h>

size_t my_strlen(const char* str)
{
	//int count = 0;
	if (*str != '\0')
	{
		return 1 + my_strlen(str + 1);
	}
	return 0;
}

int main()
{
	char arr[] = {'a','b','c','\0'};
	size_t ret = my_strlen(arr);
	printf("%d\n", ret);
	return 0;
}

5.3、指针-指针写法

/*例程5.2*/
#include <stdio.h>

int my_strlen(char* str)
{
	char* start = str;//start存储数组首元素地址
	while (*str != '\0')
	{
		str++;
	}
	return str-start;//用指针减指针获取数组元素的个数
}
int main()
{
	//strlen()---求字符串长度
	int len = my_strlen("abc");
	printf("%d\n", len);
	return 0;
}

6、指针与数组

首先,指针不等于数组,不等价
指针就是指针,指针变量就是一个存放地址的变量,指针变量的大小就是4/8个字节
数组就是数组,存放一组相同数据类型的数据,数组的大小取决于元素的类型和个数

补充:数组的数组名是数组首元素的地址(两个例外:(1)、sizeof+数组名 (2)、&+数组名),地址是可以访问指针变量中的元素

6.1、指针与数组例程1

方便理解:数组名是数组首元素的地址

/*例程6.1*/
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n",arr);//数组名是数组首元素的地址
	printf("%p\n",&arr[0]);//与首元素地址相同
	return 0;
}

6.2、指针与数组例程2 — 指针的遍历

/*例程6.2*/
#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		//printf("%p <==> %p\n",&arr[i],p+i);
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d\n",*(p+i));
	}
	return 0;
}

6.3、指针与数组例程3 — 数组间地址的关系运算1

/*例程6.3*/
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	printf("%d\n", arr[2]);//3
	printf("%d\n", 2[arr]);//3
	printf("%d\n", p[2]);//3--p[2]-->*(p+2)
	//[]是一个操作符  2和arr是两个操作数
	//推演
	//arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) == 2[arr]
	//2[arr] <==> *(2+arr)
	return 0;
}

6.4、指针与数组例程4 — 数组间地址的关系运算2

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };

	printf("%p\n", arr);//int* 类型
	printf("%p\n", arr+1);//跳过4个字节

	printf("%p\n", &arr);//&+数组名,取整个数组的大小
	printf("%p\n", &arr+1);///跳过40个字节

	printf("%p\n", &arr[0]);//int* 类型
	printf("%p\n", &arr[0]+1);//跳过4个字节

	printf("%p\n", sizeof(arr));
	return 0;
}

7、指针数组

数组的本质就是存放一组相同类型的数据
指针数组是数组,是存放指针的数组
整型数组是存放整型的数组
字符数组是存放字符的数组

7.1、指针数组例程1

/*例程7.1*/
#include <stdio.h>
int main()
{
	int arr[10];//整型数组 - 存放整形的数组就是整型数组
	char ch[5];//字符数组 - 存放字符的数组就是字符数组
	int* parr[5];//指针整型数组 - 存放指针的整型数组就是指针整型数组
	char* pch[5];//指针字符型数组 - 存放指针的字符数组就是指针字符数组
	return 0;
}

7.2、指针数组例程2 ---- 模拟二维数组

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	//指针数组
	int* arr[] = { arr1,arr2,arr3 };

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

8、二级指针

概念:用来存放一级指针地址的指针 ,也就是指向指针的指针

8.1、二级指针例程

/*例程8.1*/
#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;//p是指针变量,一级指针变量
	int* * pp = &p;//pp是指针变量,二级指针变量
	//int* p --- *指明p是指针变量,int 指明指针指向的对象是int类型的变量
	//同理:int* * pp --- 第二课*指明pp是指针变量,int*指明指向的对象是int*类型的变量

	//int*** ppp = &pp;//ppp是指针变量,三级指针变量
	//..........

	**pp = 20; //**pp == *p <---> *(*pp) == *p等价
	printf("%d\n",a);//20
	
	return 0;
}

9、结语

半亩方糖一鉴开,天光云影共徘徊。
问渠哪得清如许?为有源头活水来。–朱熹(观书有感)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值