指针小课堂

news2024/11/24 23:20:44

目录

一.内存和地址

二.指针变量和地址

1.取地址操作符(&)

2.指针变量和解引⽤操作符(*)

2.1指针变量

2.2如何理解指针类型

2.3解引用操作符

2.4 指针的解引用

2.5.不同指针类型的运加减性质

2.5.1指针与整数相加:

2.5.2指针与整数相减:

2.5.3指针运算的实际地址:

三.void*指针

四.const 修饰指针

1. const 修饰指针所指向的对象

2. const 修饰指针本身

3. const 同时修饰指针和指针所指向的对象

五.野指针

5.1野指针成应

5.2规避野指针

1.指针初始化

2.小心指针越界

3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

4.避免返回局部变量的地址

六.assert断言

使用 assert 的步骤


一.内存和地址

说到内存,内存就像一栋宿舍楼,而每一楼都有这十几二十个房间,每个房间都能住好几个人。那如果我们需要寻找某一个房间的时候,我们需要怎么找呢?答案自然是通过房间号来找了。房间号我们也称之为地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字:指针。

相对的:
内存相当于一栋宿舍楼

内存单元相当于一个房间每个内存单元取1个字节

比特位相当于一个学生(1个字节能放8个比特位)

就像这样:

所以内存单元编号=地址=指针

二.指针变量和地址

1.取地址操作符(&)

在C语⾔中创建变量其实就是向内存申请空间

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

我们打印出a的地址,如图:

再看一下他的内存所在,可以看到,其一共占用4个字节,因为a是int型的,而且值也恰好是10(0a十六进制转换为十进制为10)。

2.指针变量和解引⽤操作符(*)

2.1指针变量

我们在上面介绍了&操作符,那我们拿到了地址要怎么存放呢?

答案是用指针变量

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

#include<stdio.h>
int main() {
	int a = 10;
	int* pa = &a;//指针变量存放地址
	printf("%p\n", &a);
	printf("%p", pa);
	return 0;
}

可以看到两地址都相等:

2.2如何理解指针类型

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

* '代表着pa是一个指针变量

int则说明pa是整型类型

(注意:*可以写在左边一点,也可以右边一点,都是正确的),如:

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

  

指针类型不仅仅只有int型的还有:

字符型指针charchar* p1
短整型指针shortshort* p1
整型指针intint* p1
长整型指针longlong* p1
单精度浮点型指针floatfloat* p1
双精度浮点型指针doubledouble* p1

 (注意指针变量的类型要与变量的基本类型相同)

对于这么多的类型,我们来查看一下他们的内存大小:

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

不同平台的运行结果:

总结:

  无论是哪一种平台下计算的结果,每种指针类型的内存大小都一样,都是4或8个字节。

2.3解引用操作符

解引用操作符用于获取指针所指向的对象或变量的值

解引⽤操作符(*)。

两个例子:

int x = 10;
int* ptr = &x; // ptr 是指向 x 的指针
int y = *ptr;  // 解引用 ptr,获取 x 的值,y 现在是 10
int a = 100;
int* pa = &a;//pa指向a的地址
*pa = 0;//解引用pa,通过pa中存放的地址,找到指向的空间,
//*pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0

至于为什么弄这么复杂,直接一点定义一个变量直接赋值,或者直接让它等于零就行了,为什么还有多此一举绕来绕去呢?

当然我们可以这么做,但是的话我们多一种方法多一种途径来给他赋值或者干嘛的,何乐而不为呢,学会之后届时我们写代码的时候就可以更加灵活了。

2.4 指针的解引用

对比下面两个代码:
(1)

#include <stdio.h>
int main()
{
	int n = 0x11223344;十六进制转换为十进制结果为287454020
	int* pi = &n;
	printf("%p\n", pi);
	*pi = 0;
	printf("%d", n);//0
	return 0;
}

结果为:

pi在内存中的地址和字节:

(2)

#include <stdio.h>
int main()
{
	int n = 0x11223344;//十六进制转换为十进制结果为287454020
	char* pc = (char*)&n;//强制转换为char*型
	printf("%p\n", pc);
	*pc = 0;
	printf("%d", n);//287453952
	return 0;
}

结果为:

'

pc在内存中的地址和字节:

通过调试我们可以看到,代码(1)会将n的4个字节全部改为0,但是代码(2)只是将n的第⼀个字节改为0。

总结:  指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。 ⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

2.5.不同指针类型的运加减性质

在许多编程语言中(例如C和C++),指针与整数相加或相减是一个常见的操作。这个操作可以用来在内存中遍历数组或数据结构。

2.5.1指针与整数相加
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // ptr 指向数组的第一个元素,即 arr[0].注意单个数组名一般指向数组的第一个元素
ptr = ptr + 2; // 现在 ptr 指向 arr[2],即 30

解释:

  • 当你将一个指针与一个整数相加时,结果是一个新的指针,它指向原始指针指向的内存地址之后的某个位置。
  • 如果指针指向的是一个数组的元素,那么指针加上整数 n 将指向数组中从当前元素开始的第 n 个元素。

2.5.2指针与整数相减
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = &arr[3]; // ptr 指向数组的第四个元素,即 arr[3]
ptr = ptr - 2; // 现在 ptr 指向 arr[1],即 20

解释:

  • 当你将一个指针与一个整数相减时,结果是一个新的指针,它指向原始指针指向的内存地址之前的某个位置。
  • 如果指针指向的是一个数组的元素,那么指针减去整数 n 将指向数组中从当前元素开始的第 n 个之前的元素。
2.5.3指针运算的实际地址

解释:

  • 由于指针运算考虑了指针所指向数据类型的大小sizeof(类型),这意味着 ptr + 1 实际上是增加了 sizeof(类型) 个字节,而不是简单的增加 1
  • 例如,如果 ptr 是一个 int* 类型的指针,假设 int 类型占用 4 个字节,那么 ptr + 1 实际上是将 ptr 的地址增加了 4 个字节。

代码示例:

#include <stdio.h>
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;
}

结果如图:

2.5.4指针运算的用法

遍历数组:指针运算可以用于遍历数组中的元素。

#include <stdio.h>
int main()
{
	int arr[5] = { 10, 20, 30, 40, 50 };
	int* ptr = arr;
	for (int i = 0; i < 5; i++) {
		printf("%d ", *(ptr + i)); // 输出数组中的每一个元素
	}

	return 0;

结果如图:

指向结构体成员:指针运算还可以用于遍历结构体数组中的元素。

三.void*指针

void* 指针在C和C++中是一种通用指针类型,表示它可以指向任意类型的数据void* 指针本身不包含类型信息,只是一个内存地址,因此不能直接解引用进行指针运算

在将 void* 指针传递给其他函数时,通常需要将其转换为具体类型的指针。类型转换使用类型转换运算符 (type*)

void* ptr;
int x = 10;
ptr = &x; // void* 指向 int 类型变量

int* intPtr = (int*)ptr; // 将 void* 转换为 int* 类型
printf("%d\n", *intPtr); // 解引用 int* 类型指针,输出 10
  • void* 指针常用于需要接受不同类型数据的函数参数。
  • 例如,一个通用的比较函数可以使用 void* 指针来比较不同类型的值:
int compare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

qsort(arr, 5, sizeof(int), compare); // 使用 qsort 排序 int 类型数组

注意事项:

       

       不能直接解引用

  • 由于 void* 不包含类型信息,不能直接对其进行解引用操作。必须先将其转换为具体类型的指针,然后才能解引用。
  • 错误示例:
  • void* ptr;
    int x = 10;
    ptr = &x;
    // printf("%d\n", *ptr); // 错误:void* 不能直接解引用
    

    不能进行指针运算

  • 由于 void* 指针没有确定的类型大小,不能进行指针算术运算(如 ptr + 1)。必须将其转换为具体类型指针后再进行运算。
  • 错误示例:
  • void* ptr;
    int arr[5] = {1, 2, 3, 4, 5};
    ptr = arr;
    // ptr++; // 错误:void* 不能进行指针运算
    

四.const 修饰指针

在C和C++中,const 修饰符可以用来修饰指针及其指向的对象。这可以用来确保代码中的某些值不会被意外修改。const 可以以几种不同的方式修饰指针

1. const 修饰指针所指向的对象

const 修饰指针所指向的对象时(注意const在int*的左边),表示通过该指针不能修改所指向的对象。这个声明可以解读为“指向 int 的指针是常量”。它意味着指针本身可以改变指向不同的地址,但不能通过该指针修改所指向的值。

int x = 10;
int y = 20;
const int* ptr = &x; // ptr 指向 x

ptr = &y; // 可以改变 ptr 的指向
// *ptr = 30; // 错误:不能通过 ptr 修改 y 的值

2. const 修饰指针本身

const 修饰指针本身时(注意const在int*的右边),表示指针本身是常量不能指向其他地址。这个声明可以解读为“指针是一个常量,指向 int”。它意味着指针必须在声明时初始化,之后不能改变其指向,但可以通过指针修改所指向的对象的值。

int x = 10;
int* const ptr = &x; // ptr 必须初始化

*ptr = 20; // 可以通过 ptr 修改 x 的值
// ptr = &y; // 错误:不能改变 ptr 的指向

3. const 同时修饰指针和指针所指向的对象

const 同时修饰指针和指针所指向的对象时(注意int*两边都有const),表示指针所指向的对象不能被修改。这个声明可以解读为“指向 int 的常量指针是常量”。它意味着指针必须在声明时初始化,之后不能改变其指向,也不能通过该指针修改所指向的对象的值。

int x = 10;
const int* const ptr = &x; // ptr 必须初始化

// *ptr = 20; // 错误:不能通过 ptr 修改 x 的值
// ptr = &y; // 错误:不能改变 ptr 的指向

五.野指针

5.1野指针成应

(1). 指针未初始化

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

(2).指针越界访问

#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<=11; i++)
 {
 //当指针指向的范围超出数组arr的范围时,p就是野指针
 *(p++) = i;
 }
 return 0;
}

(3).指针指向的空间释放

#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
printf("%d\n", *p);
 return 0;
}

5.2规避野指针

1.指针初始化

如果明确知道指针指向哪⾥就直接赋值地址如果不知道指针应该指向哪⾥,可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。

初始化如下:

#include <stdio.h>
int main()
{
 int num = 10;
 int*p1 = &num;
 int*p2 = NULL;
 
 return 0;
}
2.小心指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问超出了就是 越界访问

3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL

因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL

我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起 来。

不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去 使⽤。

int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<10; i++)
 {
 *(p++) = i;
 }
 //此时p已经越界了,可以把p置为NULL
 p = NULL;
 //下次使⽤的时候,判断p不为NULL的时候再使⽤
 //...
 p = &arr[0];//重新让p获得地址
 if(p != NULL) //判断
 {
 //...
 }
 return 0;
}
4.避免返回局部变量的地址

#include<stdio.h>
int* test()
{
	int a = 0;//局部变量a出了test函数就会被销毁
	return &a;
}
int main()
{
	int* p = test();
	printf("%d\n",*p);
	return 0;
}

因为出了test函数,局部变量a就已经被销毁了,本来属于局部变量a的地址,现在却已经不是他的了。此时这块地址的指向是不确定的

六.assert断言

assert 断言是一种用于在开发和调试阶段检测程序错误的工具。它在C和C++(以及其他编程语言如Python)中被广泛使用,以验证程序运行时的假设是否为真。如果断言失败程序会中止执行,并通常会显示错误信息

使用 assert 的步骤

  1. 包含头文件

    • 在使用 assert 之前,需要包含头文件 <assert.h>
  2. 使用 assert

    • assert 宏用于检查表达式是否为真。如果表达式为假,程序会终止并显示错误信息,包括表达式、文件名和行号。

基本运用:

#include <stdio.h>
#include <assert.h>

int main() {
    int x = 5;
    assert(x == 5); // 如果 x 不等于 5,程序将终止

    printf("x is 5\n");

    x = 10;
    assert(x == 5); // 这一行将导致程序终止,因为 x 不等于 5

    printf("This line will not be executed\n");

    return 0;
}

检查指针是否为 NULL:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main() {
    int* ptr = (int*)malloc(sizeof(int));
    assert(ptr != NULL); // 检查内存分配是否成功

    *ptr = 42;
    printf("Value: %d\n", *ptr);

    free(ptr);
    ptr = NULL; // 释放内存并将指针置为 NULL

    assert(ptr == NULL); // 检查指针是否为 NULL

    return 0;
}

assert() 的使⽤对程序员是⾮常友好的,使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个宏 NDEBUG 。

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移 除这条 #define NDEBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语 句。

完!

点个赞吧,感谢阅读!

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

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

相关文章

写一个gradio录音的webui界面并展现波形图

如图下&#xff1a;这是需求 要创建一个 Gradio 录音的 Web UI 界面&#xff0c;你可以使用 Gradio 的 Audio 组件来实现。下面是一个简单的示例&#xff0c;展示了如何创建一个 Gradio 应用程序&#xff0c;其中包含一个录音按钮&#xff0c;用户可以录制音频并提交给服务器处…

JVM详解(个人学习笔记)

前言 本篇文章为我个人在学习JVM时所记录的笔记&#xff0c;内容把部分来自《深入理解java虚拟机》一书&#xff0c;笔记中总结了JVM中一些比较重要的知识点并作出了自己的解释。 java运行时数据区域 程序计数器&#xff08;线程内私有&#xff09; 程序计数器&#xff08;P…

Java每日一练_模拟面试题4(volatile和synchronized)

volatile加原子操作能取代synchronized和锁吗&#xff1f;答案是否定的。它能保证单操作原子性&#xff0c;对任意单个volatile变量的读写具有原子性&#xff0c;但对于复合操作不保证原子性&#xff0c;如x。

智慧公厕系统的重要性与发展

在城市发展的进程中&#xff0c;智慧公厕系统正逐渐成为一项不可或缺的重要设施。智慧公厕系统利用信息技术和物联网等先进手段&#xff0c;将公共厕所的建设、使用、运营和管理进行信息化整合与优化&#xff0c;实现了公厕运行的高效、智能和可持续发展。 智慧公厕系统的重要性…

MySQL —— CRUD

CRUD CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写。 我们常说增删查改&#xff0c;增删改查… 这里我们的增删查改是对表格的数据行进行操作的~~ 新增 1.1.1 单行数据 全列插入 插入一行新数据行&#xff0c;使用 insert into t…

【Bug记录】函数错误匹配,非法的间接寻址

项目场景&#xff1a; 当我写模拟vector的时候&#xff0c;写出下面测试代码准备稍微测试一下新写的构造函数 新写的构造函数&#xff0c;n个value构造 问题描述 当写出上面测试代码的时候&#xff0c;会报错&#xff1a; 这是什么鬼&#xff1f;&#xff1f;&#xff1f…

【老张的程序人生】我命由我不由天:我的计算机教师中级岗之旅

在计算机行业的洪流中&#xff0c;作为一名20年计算机专业毕业的博主&#xff0c;我深知这几年就业的坎坷与辉煌。今天&#xff0c;我想与大家分享我的故事&#xff0c;一段关于梦想、挑战与坚持的计算机教师中级岗之旅。希望我的经历能为大家提供一个发展方向&#xff0c;在计…

CCRC-CISAW信息安全保障人员证书含金量

在数字化时代背景下&#xff0c;CISAW认证受到越来越多个人的青睐。 特别是在互联网技术高速发展的今天&#xff0c;随着5G技术的广泛应用&#xff0c;市场对CISAW专业人才的需求急剧增加。 这种职业不仅地位显著&#xff0c;而且职业生涯相对较长。 目前市场上&#xff0c;…

SAP MIGO新增字段 自定义字段

效果 原先是没有的 清单里面找了没有 自定义字段 待新增字段 F1打开200 screen 加字段 zzplusl

非负数(0和正数) 限制最大值且保留两位小数,在elementpuls表单中正则自定义验证传更多参数

一、结构 <el-form-item label="单价:" prop="price"><el-inputv-model.trim="formData.price"placeholder="请输入"><template #append>(元)</template></el-input></el-form-item>二、验证方…

一个为90后设计的Shell,早知道,当年学Shell也不至于那么痛苦了,Star 25K+!

一个现代、用户友好的命令行界面&#xff0c;以其智能特性、语法高亮、实时自动建议、花式标签补全、直观的历史搜索和跨平台支持而著称。它提供了一个美观、易用且功能丰富的Shell环境&#xff0c;旨在简化Shell命令行操作&#xff0c;提高用户的工作效率。号称一个为90后设计…

数据库|SQLServer数据库:企业管理器的使用

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 之前学习了通过脚本创建数据库数据表以及增删改查的相关操作。 接下来了解企业管理器的使用。 以下为学习笔记。 01 新建数据库 1.1、登录数据库后&#xff0c;选中【数据库】-->右击【新建数据库】。 1.2、可以…

swift 自定义DatePacker

import Foundationenum AppDatePickerStyle {case KDatePickerDate //年月日case KDatePickerTime //年月日时分case kDatePickerMonth // 年月case KDatePickerSecond //秒}class AppDatePicker: UIView {private let jk_rootView UIApplication.shared.keyWindow!pri…

电池放电的速率对电池寿命有影响吗?

电池放电的速率对电池寿命确实有很大的影响&#xff0c;电池的寿命通常是指电池在正常使用条件下&#xff0c;能够保持其额定容量的时间。电池的容量会随着充放电次数的增加而逐渐减少&#xff0c;这个过程被称为电池的老化。电池的老化速度受到许多因素的影响&#xff0c;其中…

自闭症的孩子有哪些症状

在自闭症这个复杂而广阔的领域中&#xff0c;作为长期从事自闭症教育的工作者&#xff0c;我们深知每一位自闭症孩子都是独一无二的&#xff0c;他们面对的世界充满了挑战与不解。自闭症&#xff0c;也被称为孤独症谱系障碍&#xff0c;其核心症状往往体现在社交互动、沟通以及…

git安装图文

1.下载 通过百度网盘分享的文件&#xff1a;git安装图文 链接&#xff1a;https://pan.baidu.com/s/17ZMiWUIULtrGGba5n-WLeA 提取码&#xff1a;anjm --来自百度网盘超级会员V3的分享 2.安装

使用Go语言绘制水平柱状图教程

使用Go语言绘制水平柱状图教程 在本教程中&#xff0c;我们将学习如何使用Go语言及gg包绘制水平柱状图&#xff0c;并将图表保存为PNG格式的图片。水平柱状图适用于展示多个类别的数据&#xff0c;且便于标签的排列和阅读。 安装gg包 首先&#xff0c;确保你已经安装了gg包。…

远程抄表,构建智能水电管理

选自成都纵横智控官网-https://www.iotrouter.com/news/1320.html 众所周知&#xff0c;传统的人工抄表方式需要耗费大量人力资源&#xff0c;同时存在抄表难、监管难、收费难、缴费难等一系列问题。在万物互联时代下&#xff0c;物联网技术迅速发展&#xff0c;智能水电联控云…

Laya3.0 调用第三方js的方法

1.新建一个js文件&#xff0c;例如&#xff1a;SuanShi.js // 暴露类到全局作用域 ; window.SuanShi window.suan {}; (function (suan) {class JiSuan {constructor() {}static computeExpression(a, b) {return this.jia(a, b);}static jia(a, b) {return a b;}}suan.JiS…

TiDB v7.5.3 发版,听说升级后又可以躺平两年

众所周知 TiDB 运维很稳、升级丝滑&#xff0c;8 月 5 日&#xff0c;TiDB v7.5.3 发版&#xff0c;作为 TiDB v7 系列的最新长期支持版本&#xff0c;有升级需求的小伙伴可以安排起来了。 TiDB 7.5.3 发版说明 兼容性&#xff1a;新增系统表 INFORMATION_SCHEMA.KEYWORDS 用来…