C语言指针第1篇——共5篇

一、内存和地址

首先我们需要先了解换算单位:
1 bit(比特)可以存放两个二进制数
1 Byte(字节)= 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB

在这里插入图片描述

二、指针变量和地址

1、取地址操作符 &

通过取地址操作符可以找到在内存单元的编号

int main()
{
	int a = 6;
	printf("%p\n", &a);
	return 0;
}

在这里插入图片描述

2、指针变量和解引用操作符 *

那我们通过取地址操作符(&)拿到的地址是⼀个数值,比如:0x0069FF40,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?
答案是:指针变量中

int main()
{
	int a = 6;
	int * p = &a;
	printf("%p\n", p);
	return 0;
}

在这里插入图片描述
指针类型的确定主要看所指向的变量原本的类型,例:
      char a = ‘h’
      char * p = &a;

我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里必须学习一个操作符叫解引用操作符(*)。

int main()
{
	int a = 6;
	int* p = &a;
	printf("%d\n", a);
	*p = 0;
	printf("%d\n", a);
	return 0;
}

在这里插入图片描述
如上图所示,解引用操作符可以直接访问地址指向的空间,也可以改变空间所储存的数据。

3、指针变量的大小

指针变量的大小取决于地址的大小:
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)

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

在这里插入图片描述
总结:首先我们需要了解指针变量是用来存放地址的,指针变量的大小取决于存放一个地址需要多大的空间。我们只需要区分32位平台的是由32位二进制所产生的地址,而64位的平台就是由64位二进制所产生的地址。

三、指针变量类型的意义

1、指针的解引用

//代码1
int main()
{
	int n = 0x11223344; //0x表示一个十六进制数,其转化为十进制为287454020
	int* pi = &n;
	*pi = 0;
	printf("%d\n", n);  //结果为0
	return 0;
}
//代码2   
//注意:一个十六进制数占4个比特空间,2个十六进制数 = 8个比特 = 1个字节
int main()
{
	int n = 0x11223344; //共有四个字节的空间
	char* pc = (char*)&n;
	*pc = 0;
	printf("%d\n", n); //结果为287453952,改变第一个字节空间的数值44
	return 0;
}

在这里插入图片描述
根本原因是char类型本身只占一个字节的空间,(char*)&n 可以理解成读取空间中第一个字节的空间的值,后续(*pc)解引用也只能读取n原本4个字节空间的第一个字节空间。

2、指针 ± 整数

int main()
{
   int n = 10;
   char* pc = (char*)&n;
   int* pi = &n;
   printf("&n   = %p\n", &n);
   printf("pc   = %p\n", pc);
   printf("pc+1 = %p\n", pc + 1);
   printf("pi   = %p\n", pi);
   printf("pi+1 = %p\n", pi + 1);
   return 0;
}

运行结果:
在这里插入图片描述
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。

四、const 修饰指针

1、const 修饰变量

变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量的也可以修改这个变量。但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是 const 的作用。

#include <stdio.h>
int main()
{
	int m = 0;
	m = 20;//m是可以修改的
	const int n = 0;
	n = 20;//n是不能被修改的
 	return 0;
 }

2、const 修饰指针变量

首先我们需要知道 const 在修饰指针有两种情况:
第一种限制 *p 的写法(口诀在 * 左边限制 *p)

	int n = 10;
	int m = 20;
	//第一种写法:
	const int * p = &n; //注意 const 的位置
	//第二种写法:
	int const * p = &n;//两次 const 的写法不同,但效果相同
	*p = 30;//no
	p = &m;//yes

第二种限制 p 的写法 (口诀在 * 右边限制 p)

	int n = 10;
	int m = 20;
	int* const p = &n;//此时 const 在 p 左边
	*p = 30; //yes
	p = &m; //on

在实际过程中如果遇到都不想使其被修改的情况下:

	int n = 10;
	int m = 20;
	int const* const p = &n;//出现了两次的 const
	*p = 20; //on
	p = &m; //on

五、指针运算

指针的基本运算有三种,分别是:

  • 指针± 整数
  • 指针-指针
  • 指针的关系运算

1、指针 + 整数

上面已经提到指针的±运算,跟指针类型存在差异,从而根本导致运算的不同。
在这里插入图片描述
注意:数组的地址 == 数组的首项地址

#include <stdio.h>
//指针+- 整数
int main()
{
	 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	 int *p = &arr[0];  //存放的是首项的地址,也就是arr[0]的地址
	 int i = 0;
	 int sz = sizeof(arr)/sizeof(arr[0]);
	 for(i=0; i<sz; i++)
	 {
	 printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
	 }
	 return 0;
}

在这里插入图片描述

2、指针 - 指针

//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
	 char *p = s;   //*p 解引用的根本得到 s 的地址也就是'a'的地址
	 while(*p != '\0' )
	 p++;
	 return p-s;
}
int main()
{
	 printf("%d\n", my_strlen("abc"));//默认最后一位是隐藏的'\0'
	 return 0;
}

结果:
在这里插入图片描述

3、指针的关系运算

//指针的关系运算
#include <stdio.h>
int main()
{
	 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	 int *p = &arr[0];
	 int i = 0;
	 int sz = sizeof(arr)/sizeof(arr[0]);
	 while(p<arr+sz) //指针的⼤⼩⽐较
	 {
	 printf("%d ", *p);
	 p++;
	 }
	 return 0;
}

六、野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

1、野指针的成因

①指针未初始化

int main()
{ 
	 int *p;//局部变量指针未初始化,默认为随机值
	 *p = 20;
	 return 0;
}

②指针越界访问

int main()
{
	 int arr[10] = {0};
	 int *p = &arr[0];
	 int i = 0;
	 for(i=0; i<=11; i++)
	 {
		 //当指针指向的范围超出数组arr的范围时,p就是野指针
		 *(p++) = i;   //当i=11时,超过了数组的大小
	 }
	 return 0;
}

③指针指向的空间释放

int* test()
{
	 int n = 100;
	 return &n;
}
int main()
{
	 int*p = test();
	 printf("%d\n", *p);//此时n在内存中已经被释放了
	 return 0;
 }

2、如何规避野指针

①指针初始化

int main()
{
	 int num = 10;
	 int*p1 = &num;
	 int*p2 = NULL;//当遇到不确定指针指向的方向时,可以先赋值位 NULL
	 return 0;
}

②预防指针越界访问
③使用后,不再使用时,应及时赋值位NULL,使用前也应当检查其有效性
④避免返回局部变量的地址

七、 assert 断言

assert.h 头⽂件定义了宏 assert() ,用于在运⾏时确保程序符合指定条件,如果不符合,就报
错终止运行。这个宏常常被称为“断⾔”。

assert(p != NULL);

上行代码的表达意思:当 p != NULL 时继续执行程序,否则终止运行,并且给出报错信息提示。

    assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产是生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
    assert() 的使用对程序员是非常友好的,使用 assert() 有几个好处:它不仅能⾃动标识⽂件和出问题的行号,还有⼀种无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会禁用⽂件中所有的 assert() 语句。如果程序又出现问题,可以移
除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语句。
assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
    ⼀般我们可以在debug中使用,在release版本中选择禁⽤assert就行,在VS这样的集成开发环境中,在release版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在release版本不影响用户使用时程序的效率。

八、指针的使用和传址调用

1、传址调用

问题:交换 n 和 m 的值

void Swap(int x,int y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int n = 10;
	int m = 20;
	printf("n = %d  m = %d \n",n,m);//打印后方便观察
	Swap(n,m);
	printf("n = %d  m = %d \n",n,m);
	return 0;
}

在这里插入图片描述
以上代码的打印结果会发现,没有任何改变,实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。所以Swap是失败的了。

我们现在要解决的就是当调用Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,直接
将a和b的值交换了。那么就可以使用指针了,在main函数中将a和b的地址传递给Swap函数,Swap
函数里边通过地址间接的操作main函数中的a和b就好了

void Swap(int* px,int* py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int n = 10;
	int m = 20;
	printf("n = %d  m = %d \n", n, m);//打印后方便观察
	Swap(&n, &m);
	printf("n = %d  m = %d \n", n, m);
	return 0;
}

在这里插入图片描述
这里调用Swap函数的时候是将变量的地址传递给了函数,这种函数调用方式叫:传址调用

2、 strlen的模拟实现

int my_strlen(const char * arr)
{
	int count = 0;
	while (*arr != '\0')
	{
		arr++;
		count++;
	}
	return count;
}
int main()
{
	int len = my_strlen("hello");
	printf("%d\n", len);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值