深度剖析指针(上)——“C”

news2024/9/29 11:27:20

各位CSDN的uu们你们好呀,今天,小雅兰的内容是指针噢,在学习C语言的过程中,指针算是一个比较重要的内容,当然,难度也是比较大的,那么现在就让小雅兰来带大家进入指针的世界吧


字符指针

数组指针

指针数组

数组传参和指针传参


之前其实已经写过指针的博客了,但是写得不是很深入

指针——“C”_认真学习的小雅兰.的博客-CSDN博客

  • 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  • 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  • 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  • 指针的运算。

接下来,让我们详细剖析一下指针


字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	char ch = 'w';
	char* pc = &ch;//pc就是字符指针
    *pc = 'w';
	return 0;
}

char * p="abcdef";//是把字符串首字符的地址存放在p中

//表达式的值是首字符的地址,右边是一个常量字符串,常量字符串不能修改

所以,一般会在char * p前面加上一个const,使得*p的内容不允许被修改

const char * p="abcdef";

const的作用之前小雅兰也详细写过

实用调试技巧——“C”_认真学习的小雅兰.的博客-CSDN博客

char arr[]="abcdef";

char * p=arr;//p指向的是数组的首元素,arr数组是可以修改的

int main()
{
    const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}

代码 const char* pstr = "hello bit.";特别容易让人误以为是把字符串 hello bit 放到字符指针 pstr 里了,但是本质是把字符串 hello bit. 首字符的地址放到了pstr中。

 上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

下面,我们来看一道题目:

这道题目出自于《剑指offer》

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    if(str1 ==str2)
 printf("str1 and str2 are same\n");
    else

 printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");
    else

 printf("str3 and str4 are not same\n");
       
    return 0;
}

str1和str2 是两个数组,数组名表示首元素的地址,但是这两个数组不是同一块空间。

str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化 不同的数组的时候就会开辟出不同的内存块。

所以str1和str2不同,str3和str4不同。

但是,&str3和&str4就是完全不一样的啦


指针数组

采用类比法的方法,来理解就可以了。

  • 字符数组——存放字符的数组——char arr1[10];
  • 整型数组——存放整型的数组——int arr2[5];
  • 指针数组——存放的就是指针
  • 存放字符指针的数组——字符指针数组——char * arr3[5];
  • 存放整型指针的数组——整型指针数组——int * arr4[6];
#include<stdio.h>
int main()
{
	char* arr[] = { "abcdef","hehe","qwer" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

字符指针数组!!!

 

 下面,我们来利用整型指针数组,来模拟实现一个二维数组,其实之前也写过,现在再来复习一下!!!

#include<stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 6,7,8,9,10 };
	int arr3[5] = { 11,12,13,14,15 };
	//arr是一个存放整型指针的数组
	int* arr[] = {arr1,arr2,arr3};
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%-3d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 这个代码还有另外一种写法:

#include<stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 6,7,8,9,10 };
	int arr3[5] = { 11,12,13,14,15 };
	//arr是一个存放整型指针的数组
	int* arr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%-3d ", *(arr[i]+j));
		}
		printf("\n");
	}
	return 0;
}


数组指针

依旧还是采用类比法!!!

整型指针——指向整型的指针

int a=10;

int * p=&a;

字符指针——指向字符的指针

char ch='w';

char * pc=&ch;

数组指针——指向数组的指针

int arr[10];

int(*pa)[10]=&arr;//取出的是数组的地址

char arr[10];

char(*pc)[10]=&arr;

int * arr[5];

int * (*p)[5]=&arr;

 指针数组——是数组——是一种存放指针的数组

 数组指针——是指针——是一种指向数组的指针

int (*p)[10];

//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。

//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

&数组名VS数组名 

小雅兰其实之前也写过这些内容,只是涉及不深

数组——“C”_认真学习的小雅兰.的博客-CSDN博客

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?

我们看一段代码:  

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

数组名绝大部分情况下是数组首元素的地址

但是有两个例外

1.sizeof(数组名)——sizeof内部单独放一个数组名的时候,数组名表示整个数组,计算得到的是数组的总大小

2.&arr——这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样 

可见数组名和&数组名打印的地址是一样的。

难道两个是一样的吗?

我们再看一段代码: 

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr= %p\n", &arr);
	printf("&arr[0]=%p\n", &arr[0]);
	printf("arr+1 = %p\n", arr + 1);
	printf("&arr+1= %p\n", &arr + 1);
	printf("&arr[0]+1=%p\n", &arr[0] + 1);
	return 0;
}

 

可见:&arr和arr,虽然值是一样的,但是意义不一样 。

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

数组指针的使用

那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	//下标的形式访问数组
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
    return 0;
}
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
    //指针来访问
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
    return 0;
}
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
    //数组指针
	//一些别扭的写法,虽然对,但是不推荐
	//不要强行去用数组指针
	int(*p)[10] = &arr;
	int i = 0;
	//p——&arr
	//*p——*&arr
	//*p——arr
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *((*p) + i));
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", (*p)[i]);
	}
	return 0;
}

一维数组指针的使用:

#include<stdio.h>
//一维数组传参,形参是数组
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//一维数组传参,形参是指针
void print(int * arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);//与arr[i]一样
	}
}
void print(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr+i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

二维数组指针的使用:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%-3d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%-3d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%-3d ", *(*(arr+i)+j));//与arr[i][j]一样
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

学了指针数组和数组指针,我们来一起回顾并看看下面代码的意思:

int arr[5];
//整型数组
int *parr1[10];
//指针数组
int (*parr2)[10];
//数组指针
int (*parr3[10])[5];
//parr3是数组,数组中存放的是指针,该指针指向的又是数组

数组参数、指针参数

一维数组传参

#include <stdio.h>
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int* arr)//ok
{}
void test2(int* arr[20])//ok
{}
void test2(int** arr)//ok
{}
int main()
{
	int arr[10] = { 0 };//整型数组
	int* arr2[20] = { 0 };//指针数组
	test(arr);
	test2(arr2);//数组的每个元素都是int* 类型
	//一维数组传参,形参可以是数组,也可以是指针
	//当形参是指针的时候,要注意类型
}

二维数组传参

#include<stdio.h>
void test(int arr[3][5])//ok
{}
void test(int arr[][])//error
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//error
//不可以用一个整型指针来接收一行的地址
{}
void test(int* arr[5])//error
//指针数组
{}
void test(int(*arr)[5])//ok
//数组指针
{}
void test(int** arr)//error
//二级指针
//接收一级指针的地址,传过去的压根不是一个一级指针
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	//二维数组的数组名是首元素的地址,也就是第一行的地址
}
//二维数组传参,参数可以是数组,也可以是指针
//如果是数组,行可以省略,但是列不能省略
//如果是指针,传过去的是第一行的地址,形参就应该是数组指针

一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

整型变量的地址、整型指针、数组首元素的地址

二级指针传参 

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;//p是一级指针
	int** pp = &p;//pp是二级指针
	test(pp);
	test(&p);
	return 0;
}

思考:当函数的参数为二级指针的时候,可以接收什么参数?

二级指针变量、一级指针变量的地址、指针数组

void test(char** p)
{

}
int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc);
	test(ppc);
	test(arr);//Ok
	return 0;
}

好啦,小雅兰今天的内容就到这里啦,指针这个大小,总体来说难度还是很大的,所以,小雅兰也要花很多时间去消化呀,那么,函数指针和回调函数的知识点敬请期待小雅兰的下一篇博客噢!!!小雅兰加油呀!!!

 

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

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

相关文章

HiEV洞察 | 特斯拉HW4.0再爆猛料,高精定位、雷达均有变动

作者 | 查理斯 编辑 | 王博特斯拉 HW4.0 消息传出后&#xff0c;有人爆料说在硬件层面发生了巨大变化&#xff0c;引发行业轰动。大家都在猜测HW4.0 具体做了哪些改动。 2月16日&#xff0c;Twitter用户greentheonly爆出HW4.0的主板拆解照片。2月18日又爆出毫米波雷达的拆解照片…

[oeasy]python0095_乔布斯求职_雅达利_atari_breakout_打砖块_布什内尔_游戏机_Jobs

编码进化 回忆上次内容 上次 我们回顾了 电子游戏的历史 从 电子游戏鼻祖 双人网球到 视频游戏 PingPong再到 街机游戏 Pong 雅达利 公司 来了 嬉皮士 捣乱&#xff1f;&#x1f914; 布什内尔 会如何 应对 呢&#xff1f;&#x1f914; 布什内尔 布什内尔 本身就有点 …

Maven创建父子项目工程详细配置

Maven创建父子项目工程详细配置1.Maven子父工程依赖配置2.环境/版本一览&#xff1a;3.创建父工程4.创建子工程5.子模块之间引用依赖6.打包1.Maven子父工程依赖配置 你还在对Maven子父工程依赖配置感到疑惑吗&#xff1f;看了这篇文章你讲对它们有个新的认知&#xff0c;小白也…

构建对话机器人:Rasa3安装和基础入门

在开源对话机器人中&#xff0c;Rasa社区很活跃&#xff0c;在国内很多企业也在使用Rasa做对话机器人&#xff0c;有rasa开发经验的往往是加分项。 当年实习的时候接触到了Rasa&#xff0c;现在工作中也使用Rasa&#xff0c;因此&#xff0c;写写一些经验文档&#xff0c;有助后…

测试报告踩坑的点

测试报告作为测试人员的核心输出项&#xff0c;是体现自己工作价值的重要承载工具&#xff0c;需要我们认真对待&#xff0c;所以我们要重视测试报告的输出&#xff0c;那么在编写测试报告的时候&#xff0c;我们有哪些点需要注意的呢? 01 不要乱用模板 很多测试新人在编写测试…

LeetCode 周赛 334,在算法的世界里反复横跳

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 提问。 大家好&#xff0c;我是小彭。 今天是 LeetCode 第 334 场周赛&#xff0c;你参加了吗&#xff1f;这场周赛考察范围比较基础&#xff0c;整体难度比较平均&#xff0c;第一题…

使用linux部署项目步骤

文章目录前言一、服务器环境配置二、数据库导入三、项目打包1、修改项目中的访问路径2、修改db.properties的数据库访问路径3、打包4、修改配置&#xff0c;启动服务四、测试总结前言 今天学习了在服务器中部署项目&#xff0c;记录一下 一、服务器环境配置 首先要安装VMware&…

CTFer成长之路之逻辑漏洞

逻辑漏洞CTF 访问url: http://1b43ac78-61f7-4b3c-9ab7-d7e131e7da80.node3.buuoj.cn/ 登录页面用随意用户名密码登录 访问url&#xff1a; http://1b43ac78-61f7-4b3c-9ab7-d7e131e7da80.node3.buuoj.cn/user.php 登陆后有商品列表&#xff0c;共三个商品,点击购买flag 钱…

【数据结构】队列的接口实现(附图解和源码)

队列的接口实现&#xff08;附图解和源码&#xff09; 文章目录队列的接口实现&#xff08;附图解和源码&#xff09;前言一、定义结构体二、接口实现&#xff08;附图解源码&#xff09;1.初始化队列2.销毁队列3.队尾入队列4.判断队列是否为空5.队头出队列6.获取队列头部元素7…

算法练习(七)数据分类处理

一、数据分类处理 1、题目描述&#xff1a; 信息社会&#xff0c;有海量的数据需要分析处理&#xff0c;比如公安局分析身份证号码、 QQ 用户、手机号码、银行帐号等信息及活动记录。采集输入大数据和分类规则&#xff0c;通过大数据分类处理程序&#xff0c;将大数据分类输出…

Matlab 实用小函数汇总

文章目录Part.I 元胞相关Chap.I 创建空 char 型元胞Part.II 矩阵相关Chap.I 矩阵插入元素Part.III 字符串相关Chap.I 获取一个文件夹下所有文件的文件名的部分内容Part.IV 结构体相关Chap.I 读取结构体Chap.II 取结构体中某一字段的所有值本篇博文记录一些笔者使用 Matlab 时&a…

微服务框架-学习笔记

1 微服务架构介绍 1.1 系统架构演变历史 单体架构垂直应用架构&#xff1a;按照业务线垂直划分分布式架构&#xff1a;抽出业务无关的公共模块SOA架构&#xff1a;面向服务微服务架构&#xff1a;彻底的服务化1.2 微服务架构概览 1.3 微服务架构核心要素 服务治理&#xff1…

第一章 1:函数

函数概念 函数我们可以简单的理解为一个自变量只对应一个函数值&#xff0c;如图&#xff1a; 如图所示的图像&#xff0c;我们可以把其理解为函数&#xff0c;那非函数呢&#xff1f; 这个就叫做非函数&#xff0c;因为我们的一个自变量对应了两个函数值。 函数的两要素&…

k-means聚类总结

1.概述 聚类算法又叫做‘无监督学习’&#xff0c;其目的是将数据划分成有意义或有用的组&#xff08;或簇&#xff09;。 2.KMeans 关键概念&#xff1a;簇与质心 KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇&#xff0c;直观上来看是簇是一组一组聚集在一起的…

分享5款堪称神器的免费软件,建议先收藏再下载

转眼间新年已经过去一个月了&#xff0c;最近陆陆续续收到好多小伙伴的咨询&#xff0c;这边也是抓紧整理出几个好用的软件&#xff0c;希望可以帮到大家。 1.电脑安全管家——火绒 火绒是一款电脑安全软件&#xff0c;病毒库更新及时&#xff0c;界面清晰干净&#xff0c;没…

C++之父做决定了:内部自救!

进入2023年&#xff0c;技术圈都在围观大洋彼岸的聊天机器ChatGPT&#xff0c;但对于编程圈而言&#xff0c;没有什么比内存安全更能引起热议。近期美国国家安全局&#xff08;NSA&#xff09;点名批评C&#xff0c;建议使用Rust等内存安全的语言&#xff0c;霎时间让“编程语言…

Linux服务:Nginx反向代理与负载均衡

目录 一、Nginx反向代理 1、什么是代理 2、实现反向代理实验 ①实验拓扑 ②实验目的 ③实验过程 二、反向代理负载均衡 1、反向代理负载均衡调度算法 ①轮询算法 ②加权轮询算法 ③最小连接数算法 ④ip、url 哈希算法 ⑤响应时间fair算法 2、实现反向代理负载均…

Batchnorm和Layernorm的区别

在深度学习训练中&#xff0c;我们经常会遇到这两个归一化操作&#xff0c;他们之间有什么区别呢&#xff1f;我们来简单介绍一下&#xff1a; BatchNorm&#xff1a; 在深度学习训练的时候我们的数据如果没有经过预处理&#xff0c;有可能会出现梯度消失或者梯度爆炸的情况&…

aspnetcore-browser-refresh.js和Visual Studio Browser Link

我在调试ASP.NET Core web应用时&#xff0c;发现请求的页面文档底部多了一部分文件&#xff0c;而在我的页面中却没有包含&#xff0c;故查询资料&#xff0c;在此记录&#xff1a; 图中&#xff0c;可以看到红框部分是多出来了2个脚本 1.aspnetcore-browser-refresh.js 这里…

LeetCode HOT100 (23、32、33)

目录 23、合并K个升序链表 32、最长有效括号 33、搜索旋转排序数组 23、合并K个升序链表 思路&#xff1a;采用顺序合并的方法&#xff0c;用一个变量 ans 来维护以及合并的链表&#xff0c;第 i 次循i 个链表和 ans合并&#xff0c;答案保存到 ans中。 代码&#xff1a; …