C语言指针(1)

目录

一、内存和地址

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);  //输出20return 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赋值给pp = NULL;  //把野指针的数值等于NULLp = &arr[0];  //重新赋值pif (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.rhkb.cn/news/390839.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python初学者必须掌握的基础知识点

Python初学者必须掌握的基础知识点包括数据类型与变量、控制结构&#xff08;条件语句和循环语句&#xff09;、基本数据结构&#xff08;列表、元组、字典、集合&#xff09;、函数与模块、以及字符串处理等。以下是对这些基础知识点及其对应代码的详细介绍&#xff1a; 1. …

利用Llama 3 API实现盈利:细节解析

随着人工智能技术的快速发展,基于大模型的服务成为了众多初创企业关注的焦点。Llama 3 API作为一种强大的语言模型接口,为小型公司提供了利用先进AI技术的机会。本文将探讨这些小公司如何通过Llama 3 API实现盈利,并分析其中的关键因素。 一、Llama 3 API性能概览 批处理输…

Golang | Leetcode Golang题解之第318题最大单词长度乘积

题目&#xff1a; 题解&#xff1a; func maxProduct(words []string) (ans int) {masks : map[int]int{}for _, word : range words {mask : 0for _, ch : range word {mask | 1 << (ch - a)}if len(word) > masks[mask] {masks[mask] len(word)}}for x, lenX : ra…

设计模式 - Singleton pattern 单例模式

文章目录 定义单例模式的实现构成构成UML图 单例模式的六种实现懒汉式-线程不安全懒汉式-线程安全饿汉式-线程安全双重校验锁-线程安全静态内部类实现枚举实现 总结其他设计模式文章&#xff1a;最后 定义 单例模式是一种创建型设计模式&#xff0c;它用来保证一个类只有一个实…

Candance Allegro 入门教程笔记:PCB封装库的组成元素

文章目录 一、PCB封装库的组成元素二、使用Padstack Edictor制作封装焊盘引脚三、PCB Editor软件创建贴片封装&#xff08;STM32F103T8U6 QFN36 为例&#xff09;1.引入库2.读入数据 一、PCB封装库的组成元素 一般来说&#xff0c;针对于Allegro软件&#xff0c;完整的封装是由…

数据结构之《二叉树》(中)

在数据结构之《二叉树》(上)中学习了树的相关概念&#xff0c;还了解的树中的二叉树的顺序结构和链式结构&#xff0c;在本篇中我们将重点学习二叉树中的堆的相关概念与性质&#xff0c;同时试着实现堆中的相关方法&#xff0c;一起加油吧&#xff01; 1.实现顺序结构二叉树 在…

数据结构:带索引的双链表IDL

IDLindexed double list 如图&#xff0c;下方是一个双链表&#xff0c;上方是索引。索引储存为结构体数组&#xff0c;结构体内包括一个指针&#xff0c;和长度。 假设索引只有一个&#xff0c;这时&#xff0c;它应该指向双链表的中间&#xff0c;这样才能提高搜索效率。称…

MyBatis 框架的两大缺点及解决方案

MyBatis 框架的两大缺点及解决方案 1. SQL 编写负担重1.1 缺点概述1.2 解决方案 2. 数据库移植性差2.1 缺点概述2.2 解决方案 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; MyBatis 作为一款广受欢迎的 Java 持久层框架&#xff0c;尽管其…

吴恩达机器学习作业-ex7(主成分分析)

data1 导入库&#xff0c;读取数据&#xff0c;并进行可视化数据 import numpy as np import scipy.io as sio import matplotlib.pyplot as plt#读取数据 path "./ex7data1.mat" data sio.loadmat(path) # print(data.keys()) X data.get("X") # pri…

『C++实战项目 负载均衡式在线OJ』一、项目介绍与效果展示(持续更新)

文章目录 一、项目介绍二、开发环境三、第三方库四、相关技术五、项目整体框架代码目录框架 代码仓库连接 点击这里✈ 一、项目介绍 本项目是实现一个仿 leetcode 的 OJ (Online-Judge&#xff09;系统。更准确的说应该称之为leetcode 的裁剪版。因为本项目只实现了leetcode中…

《计算机网络》(第8版)第9章 无线网络和移动网络 复习笔记

第 9 章 无线网络和移动网络 一、无线局域网 WLAN 1 无线局域网的组成 无线局域网提供移动接入的功能&#xff0c;可分为两大类&#xff1a;有固定基础设施的和无固定基础设 施的。 &#xff08;1&#xff09;IEEE 802.11 IEEE 802.11 是无线以太网的标准&#xff0c;是有固定…

【保姆级系列:锐捷模拟器的下载安装使用全套教程】

保姆级系列&#xff1a;锐捷模拟器的下载安装使用全套教程 1.介绍2.下载3.安装4.实践教程5.验证 1.介绍 锐捷目前可以通过EVE-NG来模拟自己家的路由器&#xff0c;交换机&#xff0c;防火墙。实现方式是把自己家的镜像导入到EVE-ng里面来运行。下面主要就是介绍如何下载镜像和…

【初阶数据结构题目】10. 链表的回文结构

链表的回文结构 点击链接做题 思路1&#xff1a;创建新的数组&#xff0c;遍历原链表&#xff0c;遍历原链表&#xff0c;将链表节点中的值放入数组中&#xff0c;在数组中判断是否为回文结构。 例如&#xff1a; 排序前&#xff1a;1->2->2->1 设置数组来存储链表&a…

1、爬⾍概述

1. 什么是爬虫&#xff1f; 爬虫&#xff08;Web Crawler&#xff09;是一种通过编写程序自动访问并提取互联网上数据的技术。爬虫可以帮助我们在浏览网页时自动收集和保存一些有用的数据&#xff0c;例如图片、视频和文本信息。简单来说&#xff0c;爬虫就是自动化的浏览器。…

react-native从入门到实战系列教程-React Native Elements

react-native的ui框架内网真的是屈指可数&#xff0c;能用的成熟的几乎找不到。不像web端的繁荣景象&#xff0c;可以用荒凉来形容不为过。 京东的nutui说也支持react-native,官网及其简陋。尝试了未成功运行&#xff0c;可能是项目类型不同&#xff0c;对比其他类型的ui库都分…

Flink中上游DataStream到下游DataStream的内置分区策略及自定义分区策略

目录 全局分区器GlobalPartitioner 广播分区器BroadcastPartitioner 哈希分区器BinaryHashPartitioner 轮询分区器RebalancePartitioner 重缩放分区器RescalePartitioner 随机分区器ShufflePartitioner 转发分区器ForwardPartitioner 键组分区器KeyGroupStreamPartitio…

【java基础】徒手写Hello, World!程序

文章目录 前提&#xff1a;java环境变量配置使用vscode编写helloworld解析 前提&#xff1a;java环境变量配置 https://blog.csdn.net/xzzteach/article/details/140869188 使用vscode编写helloworld code .为什么用code看下图 报错了&#xff01;&#xff01;&#xff01;&…

样式与特效(3)——实现一个测算页面

这次我们使用前端实现一个简单的游戏页面,理论上可以增加很多玩法&#xff0c;&#xff0c;但是这里为了加深前端的样式和JS点击事件&#xff0c;用该案例做练习。 首先需要掌握手机端的自适应&#xff0c;我们是只做手机端玩家页面 。需要允许自适应手机端页面&#xff0c; 用…

24年电赛——自动行驶小车(H题)MSPM0G3507-编码电机驱动与通用PID

一、编码电机驱动 编码电机的详情可以查看此篇文章&#xff1a; stm32平衡小车--&#xff08;1&#xff09;JGB-520减速电机tb6612&#xff08;附测试代码&#xff09;_jgb520-CSDN博客 简单来说&#xff0c;编码电机的驱动主要是给一个 PWM 和一个正负级就能驱动。PWM 的大小…

9-springCloud集成nacos config

本文介绍spring cloud集成nacos config的过程。 0、环境 jdk 1.8maven 3.8.1Idea 2021.1nacos 2.0.3 1、项目结构 根项目nacos-config-sample下有两个module&#xff0c;这两个module分别是两个springboot项目&#xff0c;都从nacos中获取连接mysql的连接参数。我们开工。 …