C语言指针(1)

news2024/9/22 13:36:30

目录

一、内存和地址

1、生活中的例子

2、内存的关系

二、指针变量和地址

1、&符号,%p占位符

2、一个简单的指针代码。

3、理解指针

4、解引用操作符

5、指针变量的大小。

三、指针变量类型的意义

1、指针解引用的作用

2、指针+指针

3、指针-指针

4、void*指针

四、const修饰指针

1、const修饰变量

2、const修饰指针变量

1、没有const

2、const在*左边

3、const在*右边

4、const在*两边

五、指针运算

1、指针+-整数

2、指针-指针

3、指针的关系运算

六、野指针

1、野指针的成因

1、未初始化

2、越界访问

3、指针指向空间的释放

2、如何避免野指针

七、assert断⾔

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

1、strlen的模拟实现

2、传值调用传值调用

1、传值调用

2、传址调用


hello大家好,今天我们来讲一下C语言中指针的知识点,指针这部分内容很多,容易记混而且有时候写代码也想不到,这就需要有一个良好的指针基础,我尽全力把指针用我们已经学习过的知识来讲述,让学习者都可以很快的理解,并且可以使用我所讲指针给出的例子来练习。

那么我们就开始学习吧!!!

一、内存和地址

1、生活中的例子

学习指针我们就要知道内存和地址的关系。

下面图中,我们可以把这一栋楼看做内存,每个房间可以看做地址。

如果我们想去到楼里的特定房间,我们就要挨个房间寻找我们想要去的特定房间里面,然后我们再去到另外一个特定的房间里面,我们还需要挨个寻找,这样效率会非常慢。

聪明的同学已经开始想到了,我们可以把每个房间都设置门牌号,这样我们就可以快速去到我们想去的房间了。

结论:门牌号=地址=指针

2、内存的关系

我们可以把每把内存给划分为内存单元,一个内存单元大小等于一个字节,所以每个房间就可以看着是字节。

常见的存储单位:bit(比特)-> Byte(字节) -> KB -> MB -> GB -> TB -> PB

1字节 = 8bit位

二、指针变量和地址

1、&符号,%p占位符

取地址符号(&):可以让我们获取地址。

%p:想打印出地址就需要使用%p这个占位符。

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

输出:

2、一个简单的指针代码。

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

通过调试vs我们看见p=&a。

在这段代码中,我们设置了a=10,我们通过*p指向了a的地址,此时p就等于a的地址。我们就可以通过*p来修改a中的地址。

3、理解指针

int a = 10;
int * pa = &a;

pa左边写的是int*,*说明是pa的指针变量,而int在说明pa指向的是一个整型类型的对象。

4、解引用操作符

解引用操作符就是“ * ”。

int main()
{
	int a = 10;
	int* p = &a;
	*p = 20;
	printf("%d ", *p);  //输出20
	return 0;
}

这段代码中,我们通过*p=20;修改a的数值。尽管int* p = &a;这一行代码我们已经获取了a的地址,但是我们还是需要通过操作符“ * ”来访问或修改这样地址指向的值。p中存放的内容是a的地址,如果我们直接写p=20,程序修会报错,因为p是一个指针,不能直接存放一个整数。只有我们使用*p,我们才能直接访问p指向的内存空间,即a的内存空间,从而修改变量中的a,此时a的数值也会跟着改变。

5、指针变量的大小。

指针变量的大小是取决于我们使用的是32位平台还是64位平台。

int main()
{
	printf("%d ", sizeof(char*));
	printf("%d ", sizeof(int*));
	printf("%d ", sizeof(double*));
	printf("%d ", sizeof(float*));
	return 0;
}

32位平台输出:

64位平台输出:

在32位下,指针的大小位4个字节。

在64位下,指针的大小位8个字节。

三、指针变量类型的意义

1、指针解引用的作用

int main()
{
	int a = 0x11223344;
	char* p = (char*)&a;
	*p = 0;
	return 0;
}

*p运行前&a地址:

*p运行后&a地址:

为什么只有一个字节等于0,因为*p是一个char类型,char类型只占一个字节。

只有*p跟a是一个类型的时候,才可以把a=10。

2、指针+指针

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

输出:

我们可以看到,p1指针是char类型,p2是int类型,当把指针+1的时候,p1和p2所得到的地址是不一样的,p1加1字节,p2加4字节,这是因为指针的类型是不一样的。

结论:指针的类型决定的指针向前的步子是多大。

3、指针-指针

int main()
{
	int a = 10;
	char* p1 = (char*)&a;
	int* p2 = &a;
	printf("%p\n", &a);
	printf("%p\n", p1);
	printf("%p\n", p1 - 1);
	printf("%p\n", p2);
	printf("%p\n", p2 - 1);
	return 0;
}

输出:

4、void*指针

int main()
{
	int a = 10;
	char* p = &a;
	*p = 20;
	return 0;
}

char* p = &a;由于我们使用char类型接收int类型,导致编译器提示错误。

void*可以接收任意指针,char,int,long,double等等

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

但是void*是无法修改指针变量的

int main()
{
	int a = 10;
	int b = 20;
	void* p1 = &a;
	void* p2 = &b;
	*p1 = 30;
	*p2 = 40;
	return 0;
}

那么void*类型的指针有什么用呢?

void*是使用函数参数部分,用来接收任意不同类型的数据,可以实现泛编程效果,使得一个函数可以出来多个数据类型。

四、const修饰指针

1、const修饰变量

const这个函数可以让变量中避免被修改。

int main()
{
	const int a = 10;
	a = 20;
	return 0;
}

但是我们仍然可以使用指针来修改a变量

int main()
{
	const int a = 10;
	int* p = &a;
	*p = 20;
	printf("%d", *p);
	return 0;
}

输出:

那么就有人问了,这有什么意思,这不就是防君子不防小人吗,那么我们如果才可以防止a被修改呢?

2、const修饰指针变量

这些有什么区别呢?

int* p;          //没有const
const int* p;     //const在*左边
int const* p;      //const在*左边
int* const p;      //const在*右边
int const * const p;      //const在*两边

1、没有const

void test1()
{
	int n = 10;
	int m = 20;
	int* p = &n;
	*p = 20;
	p = &m; 
}

在没有const限制的条件下我们可以修改指针*p的变量,p也可以获得m的地址

2、const在*左边

const在*左边有两种写法,这两种写法都是正确的:

const int* p =&a;
int const* p =&a;
void test1()
{
	int n = 10;
	int m = 20;
	const int* p = &n;
	*p = 20;
	p = &m; 
}

在const限制的条件下我们不可以修改指针*p的变量,但是p可以获得m的地址.

3、const在*右边

void test1()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20;
	p = &m; 
}

const限制的条件下我们可以修改指针p的数值,但是p不可以获得m的地址。

4、const在*两边

void test1()
{
	int n = 10;
	int m = 20;
	int const* const p = &n;
	*p = 20;
	p = &m; 
}

const限制的条件下我们不可以修改指针p的数值,p也不可以获得m的地址。

总结:

  1. const在左边的时候,修饰的是指针指向的内容,保证指向内容不能被指针修改,但是指针变量本身的内容是可变的。
  2. const在右边的时候,修改是是指针变量本身,保证指针指向的内容是可以被修改的,但是指针变量本身是无法被修改的

五、指针运算

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

下图是数组中每一个元素的下标

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;
}

指针*p的类型是int,地址是&arr[0],下标是0,通过p+i,我们就可以打印出指针指向每一个元素下标地址,通过解引用我们从而获得打印数组。

我们之前学习的是printf("%d ", arr[i]);这样打印出数组,但是在编译器底层是通过指针来打印的,*(p + i)= arr[i]。

2、指针-指针

#include <stdio.h>
int my_strlen(char* s)
{
	char* p = s;
	int count = 0;
	while (*p != '\0')
	{
		count++;
		p++;
	}
	return count;
}

int main()
{
	printf("%d\n", my_strlen("abcdef"));
	return 0;
}

输出字符串长度:

---------------------------------------------------------------------------------------------------------------------------------

int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;  //当p结束是指向字符串的末尾,所以我们用末尾-初始值就是字符串长度
}

int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

3、指针的关系运算

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;
}

while (p < &arr[sz])这一行代码,我们是利用地址来进行比较的,当p这个地址小于&arr[sz]地址的时候,那么就不允许。

六、野指针

指针指向实际方向的内容是否有效,超出指针指向变量部分的内容,或者是栈帧销毁的内容会成为野指针,野指针的数值是一个随机值。

1、野指针的成因

1、未初始化

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

2、越界访问

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = &arr[0];
	for (int i = 0; i < 11; i++)
	{
		printf("%d ", *(p+i));
	}
	return 0;
}

输出:

在这个一维数组打印中,指针超出了指向数组的值,造成了随机值,这就是野指针。

3、指针指向空间的释放

int test()
{
	int n = 100;
	return &n;
}

int main()
{
	int* p = test();
	printf("%d", *p);
	return 0;
}

在这一段代码中*p也是一个野指针,这是为什么,因为我们创建的test函数在运行完以后就会销毁,所以*p指向的是&n,test函数已经被销毁了,*p就没有地址了,导致*p变成了野指针。

2、如何避免野指针

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

我们可以在创建的int* p里放入NULL,就可以避免野指针出现。

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = &arr[0];
	for (int i = 0; i < 11; i++)
	{
		*(p++) = i;
	}
//p已经越界,把NULL赋值给p
	p = NULL;  //把野指针的数值等于NULL
	p = &arr[0];  //重新赋值p
	if (p!=NULL)
	{
		printf("haha\n");
	}
	return 0;
}

七、assert断⾔

assert头文件:

assert (p!=NULL);

assert是一个对程序员很友好的功能,如果assert这个程序符合调价,那么就继续运行,如果不符合那就会提示错误在哪些地方。

如果代码没有任何问题以后,可以在头文件上面写:

#define NDEBUG

#include <assert.h>
int main() 
{
    int age = 11;
    // 使用assert检查年龄是否大于18岁
    assert(age >= 18);
    printf("年龄大于18岁。\n");
    return 0;
}

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

1、strlen的模拟实现

方法一:

int my_strlen(const char* str)
{
	int count = 0;
	assert(str);
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	int len= my_strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

方法二:

int my_strlen(const char* str)
{
	char* p = str;
	assert(str);
	while (*p)
	{
		p++;
	}
	return p-str;
}

int main()
{
	int len= my_strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

输出:

2、传值调用传值调用

1、传值调用

void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(a, b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

为什么明明在函数中交换了a和b的数值,为什么没有交换成功?

2、传址调用

void Swap2(int* px, int* py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d b=%d\n", a, b);
	Swap2(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

输出:

指针并不是跟形参一样是拷贝,指针指向的数据就是a,b这两个地址,当函数Swap2中px,py中数据进行交换的时候,交换的就是main函数中a,b中真实的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1976970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode3224. 使差值相等的最少数组改动次数

Every day a Leetcode 题目来源&#xff1a;3224. 使差值相等的最少数组改动次数 解法1&#xff1a; 想一想&#xff0c;什么情况下答案是 0&#xff1f;什么情况下答案是 1&#xff1f; 如果答案是 0&#xff0c;意味着所有 ∣nums[i]−nums[n−1−i]∣ 都等于同一个数 X。…

【JVM内存】系统性排查JVM内存问题的思路

【JVM内存】系统性排查JVM内存问题的思路 背景 前言 遇到过几次JVM堆外内存泄露的问题&#xff0c;每次问题的排查、修复都耗费了不少时间&#xff0c;问题持续几月、甚至一两年。我们将这些排查的思路梳理成一套系统的方法&#xff0c;希望能给对JVM内存分布、内存泄露问题…

有序矩阵中第K小的元素(LeetCode)

题目 给你一个 n x n 矩阵 matrix &#xff0c;其中每行和每列元素均按升序排序&#xff0c;找到矩阵中第 k 小的元素。 请注意&#xff0c;它是 排序后 的第 k 小元素&#xff0c;而不是第 k 个 不同 的元素。 你必须找到一个内存复杂度优于 的解决方案。 解题 from queue i…

DFS之搜索顺序与剪枝

搜索顺序&#xff1a; 1.https://www.acwing.com/problem/content/1119/ 首先&#xff0c;我们考虑一个贪心&#xff1a; 假如说A的倒数K个字符恰好与B的前K个字符重合&#xff0c;那么我们就连接。 也就是说我们一旦匹配就直接相连而不是继续找更长的重合的一段子串。 因…

秋招突击——算法练习——8/3——马上消费笔试总结——{距离为一的字符串、组合数遍历}

文章目录 引言正文第一题&#xff1a;距离为1的字符串个人实现修正实现 第二题&#xff1a;三角形数个人实现反思实现比较对象使用equalsCollections.sort方法 总结 引言 今天的笔试难度不算大&#xff0c;但是自己的做的很糟糕&#xff0c;发现了很多问题&#xff0c;很多模板…

目标检测,目标跟踪,目标追踪

个人专做目标检测&#xff0c;目标跟踪&#xff0c;目标追踪&#xff0c;deepsort。YOLOv5 yolov8 yolov7 yolov3运行指导、环境配置、数据集配置等&#xff08;也可解决代码bug&#xff09;&#xff0c;cpu&#xff0c;gpu&#xff0c;可直接运行&#xff0c;本地安装或者远程…

JVM-类加载器和双亲委派机制

什么是类加载器&#xff1f; 类加载器是Jvm的重要组成之一&#xff08;类加载器、运行时数据区、执行引擎、本地库接口、本地方法库&#xff09;&#xff0c;负责读取java字节码并将其加载到Jvm中的组件 类加载器的分类 Java中的类加载器可以分为以下几种&#xff1a; 1. 启…

Yolov8在RK3588上进行自定义目标检测(一)

1.数据集和训练模型 项目地址&#xff1a;https://github.com/airockchip/ultralytics_yolov8.git 从github(htps:l/github.com/airockchip/ultralytics_yolov8)上获取yolov8模型。 下载项目&#xff1a; git clone https://github.com/airockchip/ultralytics_yolov8.git …

进程的虚拟内存地址(C++程序的内存分区)

严谨的说法&#xff1a; 一个C、C程序实际就是一个进程&#xff0c;那么C的内存分区&#xff0c;实际上就是一个进程的内存分区&#xff0c;这样的话就可以分为两个大模块&#xff0c;从上往下&#xff0c;也就是0地址一直往下&#xff0c;假如是x86的32位Linux系统&#xff0c…

InstantID节点安装遇到的问题与解决办法

原来在一台没有显卡支持的电脑上安装InstantID节点使用没有问题&#xff0c;将安装好的ComfyUI&#xff08;简称ComfyUI_CPU_OK包&#xff09;复制到一台有显卡支持的电脑上&#xff0c;竟然发现InstantID节点异常不能使用&#xff08;按道理应该能正常运行才对&#xff09;&am…

吴恩达机器学习L1W3L06-逻辑回归的梯度下降

目标 在本实验室&#xff0c;你可以看到 更新逻辑回归的梯度下降。在一个熟悉的数据集上探索梯度下降 import copy, math import numpy as np %matplotlib widget import matplotlib.pyplot as plt from lab_utils_common import dlc, plot_data, plt_tumor_data, sigmoid,…

SQL注入实例(sqli-labs/less-3)

0、初始页面 1、确定闭合字符 确定为字符型注入 ?id1 and 11 ?id1 and 12 确定闭合字符为 ‘) ?id1 ?id1) 2、确定表的列数 确定查询表的列数为3 ?id1) order by 3 -- 3、确定回显位置 确定回显位置为第二列和第三列 ?id-1) union select 1,2,3 -- 4、爆库名 …

MySQL数据库——数据库基础

二、数据库基础 1.主流数据库 SQL Sever:微软的产品&#xff0c;.Net程序员的最爱&#xff0c;中大型项目。Oracle:甲骨文产品&#xff0c;适合大型项目&#xff0c;复杂的业务逻辑&#xff0c;并发一般来说不如MySQL。MySQL&#xff1a;世界上最受欢迎的数据库&#xff0c;属…

《Advanced RAG》-01-朴素RAG存在的问题

摘要 文章阐述了RAG技术如何通过整合外部知识源来提升大型语言模型&#xff08;LLM&#xff09;的性能&#xff0c;使其能够产生更精确、上下文感知的回应&#xff0c;并减少幻觉现象。 自2023年以来&#xff0c;RAG已成为基于LLM的系统中最流行的架构&#xff0c;许多产品依赖…

一文彻底搞懂Fine-tuning - 预训练和微调(Pre-training vs Fine-tuning)

最近这一两周看到不少互联网公司都已经开始秋招提前批了。不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解…

推荐算法学习记录2.2——kaggle数据集的动漫电影数据集推荐算法实践——基于内容的推荐算法、协同过滤推荐

1、基于内容的推荐&#xff1a; 这种方法根据项的相关信息&#xff08;如描述信息、标签等&#xff09;和用户对项的操作行为&#xff08;如评论、收藏、点赞等&#xff09;来构建推荐算法模型。它可以直接利用物品的内容特征进行推荐&#xff0c;适用于内容较为丰富的场景。‌…

VBA学习(22):动态显示日历

这是在ozgrid.com论坛上看到的一个贴子&#xff0c;很有意思&#xff0c;本来使用公式是可以很方便在工作表中实现日历显示的&#xff0c;但提问者因其需要&#xff0c;想使用VBA实现动态显示日历&#xff0c;即根据输入的年份和月份在工作表中显示日历。 下面是实现该效果的VB…

web、nginx

一、web基本概念和常识 ■ Web:为用户提供的一种在互联网上浏览信息的服务,Web服务是动态的、可交互的、跨平台的和图形化的。 ■ Web 服务为用户提供各种互联网服务,这些服务包括信息浏览服务,以及各种交互式服务,包括聊天、购物、学习等等内容。 ■ Web 应用开发也经过了几…

C#中计算矩阵(数学库下载和安装)

1、一步步建立一个C#项目 一步步建立一个C#项目(连续读取S7-1200PLC数据)_s7协议批量读取-CSDN博客文章浏览阅读1.7k次&#xff0c;点赞2次&#xff0c;收藏4次。这篇博客作为C#的基础系列&#xff0c;和大家分享如何一步步建立一个C#项目完成对S7-1200PLC数据的连续读取。首先…

如何解决C#字典的线程安全问题

前言 我们在上位机软件开发过程中经常需要使用字典这个数据结构&#xff0c;并且经常会在多线程环境中使用字典&#xff0c;如果使用常规的Dictionary就会出现各种异常&#xff0c;本文就是详细介绍如何在多线程环境中使用字典来解决线程安全问题。 1、非线程安全举例 Dictio…