首页 > 其他分享 >C语言指针修仙

C语言指针修仙

时间:2024-09-16 20:55:14浏览次数:11  
标签:arr return 变量 int C语言 修仙 地址 指针

文章目录


引入

你问我C语言精髓是什么,那包是指针系列最精彩呀,本文介绍了指针部分最基础的内容

内存和地址

内存

内存划分为一个个小的内存单元,一个内存单元的大小是一个字节,每一个字节都有一个专属的编号,内存单元的编号又叫地址
当电脑的CPU在内存上读取数据时,通过内存单元的地址来找到数据并读取
存在如下理解:内存单元的编号 = 地址 =指针

编址

每一个内存单元都有一个特殊的编号,这些编号记录在哪里呢?实际上,计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的,就像是钢琴上不需要写“剁、来、咪、发、唆、拉、西”这样的信息,但演奏者照样能够准确找到每⼀个琴弦的每⼀个位置,?因为制造商已经在乐器硬件层⾯上设计好了,并且所有的演奏者都知道。本质是⼀种约定出来的共识

指针变量和地址

取地址操作符(&)

思考:创建变量的本质是什么?
事实上就是向内存申请空间,申请了空间,要怎么得到这块空间的地址呢? 哎~取地址操作符这不就来了么。

#include<stdio.h>
int main()
{
	int a = 10;
	printf("%p", &a);
	return 0;
}

这样一个代码就可以打印出a的地址,我们看到在打印时,用到了一个操作符&这里可不是按位与,而是取地址操作符,简单理解就是可以取得变量的地址
int类型的变量在内存中占用四个字节的空间,每一个字节都有一个专门的地址,这里取a的地址取得的是第一个字节的地址

解引用操作符(*)

指针变量

我们获取到一个变量的地址后要存储指针变量中

int main()
{
	int a = 10;
	int* pa = &a;
	return 0;
}

指针变量和其他任何一种变量都一样,就像是整形变量是用来存一整数,指针变量就是用来存一指针,指针又叫地址,所以我也想为啥不直接叫地址变量,就是想告诉读者,指针变量没有啥特别的

理解指针变量

我们看到指针变量是包含三个部分的,分别是int * pa,一颗星表示这里的变量是指针变量,星左边的是指针变量的类型,星的右边是指针变量的变量名

解引用操作符

取到了一个变量的地址,要怎们通过地址来使用这个变量呢?那包是解引用操作符啊

int main()
 {
 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;
 }

*pa翻译就是:通过pa里存储的地址,找到这个地址对应的变量

指针变量的大小

指针变量就是用来存储一个地址的变量,要想弄明白指针变量有多大,就要清楚,一个地址有多大
我们假设一个32位机器有三十二根地址总线,每根地址线都能发出电信号,这些电信号转化为数字信号就是0或1,我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。同理,64位机器产生的地址就要8字节来存储

指针变量的类型和意义

看完指针变量的大小我们知道,不管是什么类型的指针变量大小都是4或8个字节,那么指针变量的类型有啥用呢?
一句话概括指针变量类型的意义就是,决定了访问变量时的步长,我们通过下面的代码来理解

指针的解引用

int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	/*char* pc = (char*)&n;
	*pc = 0;*/
	return 0;
}

在这里插入图片描述
在这里插入图片描述

int main()
{
	int n = 0x11223344;
	/*int* pi = &n;
	*pi = 0;*/
	char* pc = (char*)&n;
	*pc = 0;
	return 0;
}

在这里插入图片描述
直接看图是不是能瞬间理解指针的类型决定指针访问时的步长的含义?
代码一,我们把n的地址存放在一个整形指针中,然后通过指针来修改n的值,结果是正确的修改
代码二,我们把n的地址存放在一个字符型指针中,发现只改了一个字节的值
这个结果就说明,指针便变量的类型决定了指针的访问时时的步长

指针 ± 整数

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

在这里插入图片描述
我们可以看出,char* 类型的指针变量+1跳过1个字节,int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

指针运算

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

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

指针加减±整数

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]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p+i));
	}
	return 0;
}

这里实际上是因为数组在内存中连续存放,所以可以通过这种方式来访问

指针-指针

int my_strlen(char *s)
{
	char *p = s;
	while(*p != '\0' )
			p++;
	return p-s;
}
int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

当两个指针指向同一块区域,可以通过指针-指针的方式来计算两指针之间的元素个数

指针的关系运算

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz)//数组名表示数组首元素的地址
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
大白话就是说:你没有权限访问,但是访问了

int main()
{
	int* p;
	*p = 20;
	return 0;
}

指针变量p中是没有存任何地址的,再解引用赋值,就导致了野指针的使用

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++) = i;
	}
	return 0;
}

这里对数组进行了越界访问,超出了p应该访问的范围,导致野指针的使用

int* func()
{
	int n = 100;
	return &n;
}

int main()
{
	int *p = func();
	printf("%d ", *p);
}

当调用完func函数,变量n已经被销毁,此时再去使用n原本地址就是非法访问,导致野指针的使用

规避野指针

使用指针前初始化

int main()
 {
	 int num = 10;
	 int*p1 = &num;
	 int*p2 = NULL;
	 return 0;
 }

创建的指针变量*p2没有指向,就要给其赋值为NULL

小心指针越界

使用指针访问数组时,要看清楚指针是否越界访问

及时置空,使用前检测指针有效性

当指针变量不再使用后,要将指针变量及时置空,使用指针变量之前,也要检查指针是否为空

assert断言

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

//#define NDEBUG 
#include<assert.h>
void func(int* p, int sz)
{
	assert(p);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	func(arr, sz);
	return 0;
}

就是检测传来的指针变量是否为空指针,define NDEBUG 相当于一个开关,来控制断言是否生效

总结

以上就是指针部分的一些基本概念,常识
感谢阅读 ^ - ^


标签:arr,return,变量,int,C语言,修仙,地址,指针
From: https://blog.csdn.net/Zach_yuan/article/details/142280850

相关文章

  • C语言小游戏:猜数字 惩罚关机
    #define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<time.h>#include<stdlib.h>#include<windows.h>//菜单voidmenu(){   printf("******************\n");   printf("*****1.play*****\n");   prin......
  • C语言main(主)函数介绍
    HelloWorldC语言main(主)函数介绍先展示一个标准的main程序#include<stdio.h>intmain(intargv,char*argc[]){return0;}这个程序是很多初学者学习C语言第一个程序,如果你现在将这个程序复制粘贴到文本编辑器并编译执行会发现,什么都没有!分编译器或IDE不同有的执行后会......
  • C++从小白到大牛:C++智能指针的使用、原理和分类
    C++从小白到大牛:C++智能指针的使用、原理和分类引言在C++编程中,指针是一个强大但危险的工具。它们允许直接操作内存,但也可能导致内存泄漏、悬空指针等问题。为了解决这些问题,C++引入了智能指针(SmartPointers)的概念。智能指针是一种封装了原始指针的对象,它们自动管理内存的生命周期......
  • 掌握C语言动态内存分配:从入门到精通,一次搞定!
    在C语言开发中,内存管理是一个非常重要但常被忽略的话题。与一些高级语言(如Java或Python)不同,C语言不会自动管理内存,开发者需要自己处理内存的分配和释放。虽然这种灵活性为程序的优化提供了巨大的可能性,但它也意味着更高的风险:如果不小心,就容易引发内存泄漏、空指针错误、内存越......
  • C语言学习进阶路线图
    目录一、基础准备1.1.了解计算机基础知识1.2.安装开发环境二、入门学习2.1.学习C语言基本语法2.2.编写简单程序三、进阶概念3.1.函数与模块3.2.数组与字符串3.3.指针基础四、深入探索4.1.指针高级应用4.2.结构体与联合体4.3.文件操作五、高级特性5.1......
  • C语言中的GCC的优化和数组的存放方式、Cache机制、访问局部性
    “我们仍需共生命的慷慨与繁华相爱,即使岁月以刻薄和荒芜相欺”文章目录前言文章有误敬请斧正不胜感恩!第一题:***什么是gcc:***C语言中,“gcc-O2”是使用GCC编译器时的一个编译选项。第一部分:为什么程序一输出0,而程序二输出1?第二题:第二部分:为什么两个循环版本的性能......
  • C++ 成员函数指针简单测试
    classDog{public:voidUpdate_Func(shorti);short(Dog::*pfunc)(short);std::function<short(short)>ffunc;public:shortgoodMorning(shortid);shortgoodAfternoon(shortid);};voidDog::Update_Func(shorti){switch(i)......
  • 鹏哥C语言39---分支/循环语句练习:猜数字游戏
    #define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<time.h>//voidfun(inta[]) //因为传过来的是地址,所以应该用一个指针变量来接收,故这里的a本质上是个指针变量//{//   printf("%zu",sizeof(a));//输出8 在x64下,指针大小是......
  • 【C语言】 结构体与位段
    系列文章目录C结构体与位段文章目录系列文章目录前言一、结构体的定义与声明1.结构体的定义2.结构体类型的声明结构的声明结构体变量的创建和初始化3.结构的特殊声明4.结构的自引用二、结构体内存对齐1.对齐规则为什么存在内存对齐?修改默认对齐数三、结构体传参......
  • Python调用C语言动态链接库
    调用方法如果觉得Python性能不够,可以使用C、C++或Rust、Golang为按标准C类型。为Python编写扩展。Python通过自带的ctypes模块,可以加载并调用C标准动态链接库(如.ddl或.so)中的函数。常用的操作为:importctypes#加载动态链接库lib=ctypes.CDLL("./xxx.so")#声明要调......