字符指针?指针数组?数组指针?《C语言指针进阶第一重奏》

news2024/11/27 22:27:07

目录

一.字符指针

1.1字符指针的认识 

1.2字符指针存放字符串

1.3字符指针的使用

二.指针数组

2.1指针数组的认识

三.数组指针

3.1数组指针的认识

3.2数组名和&数组名的区别

3.3数组指针的使用

 3.4数组参数,指针参数

3.5一维数组传参

3.6二维数组传参

3.7一级指针传参

3.8二级指针传参



一.字符指针

1.1字符指针的认识 

字符指针的认识:其实很简单,跟其他类型类比一下就知道了,请看下面的代码。

#define _CRT_SECURE_NO_WARNINGS 1


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

int main()
{
	int a = 10;        //定义一个整型变量
	printf("%d\n", a);
	int* p = &a;       //将变量a的地址存放的整型指针变量p中
	*p = 15;           //对p解引用改变a地址的内容,即把15换成10
	printf("%d\n", a);
	printf("-------------\n");
	char c = 'A';      //定义一个字符变量
	printf("%c\n", c); //将变量c的地址存放的字符指针变量pc中
	char* pc = &c;     对pc解引用改变a地址的内容,即把'A'换成'C'
	*pc = 'C';
	printf("%c\n", c);

	return 0;
}

 跟整型指针类型用法一样,都是指针用来存放变量的地址,解引用改变地址的内容,不过是把类型不同,整型指针存放的是整型类型变量的地址,字符指针存放的是字符类型变量的地址。

1.2字符指针存放字符串

字符指针我们知道它可以存放一个字符变量的地址,那如果是存放的字符串会怎么样呢?

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

int main()
{
	char arr[] = "hello classmate!";
	printf("%s\n", arr);
	printf("%c\n",*arr);
	printf("----------------\n");
	const char* pstr = "hello classmate!";
	printf("%s\n", pstr);
	printf("%c\n", *pstr);
	return 0;
}

分析以上代码,你也会惊奇的发现,字符指针存放字符串变量的方式和数组的数组名表示首元素地址也很类似,根据上面代码我们具体分为上下两部分。

第一部分:定义了一个arr数组名的字符数组,数组名就是数组首元素地址,所以数组名arr就是字符‘h’的地址,而且我们字符串打印时一般都只需要输入数组名就可以打印字符串了,而这也说明了,字符串的打印只要给首元素的地址就可以进行字符串打印了。arr既然是首元素地址,对arr进行解引用就是首元素。

第二部分:跟第一部分极其类似,我们定义一个pstr的字符指针,用来存放字符串的地址,结果打印发现,pstr 存放的不是整个 "hello classmate" 的字符串的地址存放的就是首字符 'h'的地址

打印字符串和pstr解引用与上面的数组也是同一道理,就不赘述了。至于为什么要加const,是因为pstr存放的是常量字符串,既然是常量,所以就加const防止被修改。

1.3字符指针的使用

你先看一下代码,自己小小研究一番。

#include <stdio.h>
int main()
{
    char str1[] = "hello friend.";
    char str2[] = "hello friend."; 
    const char *str3 = "hello friend.";
    const char *str4 = "hello friend.";
    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我们都知道是数组首元素地址,然后你发现都是首字符‘ h ’的地址,可能会觉得相同str1 == str2,不要被这样的错觉蒙蔽你明亮的双眼。str1 和 str2 是两个数组,在内存开辟的是两块不同的空间,就算内容是一样的,地址也不可能一样,两者的首元素地址自然也不一样,所以str1 不等于str2。

再看字符指针str3,str4,存放都是字符' h '的地址,但因为str3和str4是指针。当指向同一个字符串的时候,他们实际会指向同一块内存,实际就是同一块地址,简单点讲就是,str3 和str4用的是同一块地址。

二.指针数组

2.1指针数组的认识

什么是指针数组呢,是数组呢还是指针呢,以后咱们就这样判断,谁放在后面就是谁指针数组,数组放在后面,所以指针数组是数组,是用来存放指针的数组。

#include<stdio.h>

int main()
{
	int arr1[10]; //arr1存放了10个元素,类型是整型
	char arr2[10];//arr2存放了10个元素,类型是字符型
	int* arr3[10];//arr3存放了10个元素,类型是整型指针
	char* arr4[10];//arr4存放了10个元素,类型是字符指针
	return 0;

}

 因为指针数组的内容很浅,所以这一点内容应该可以理解了,我们重点放在数组指针上。

三.数组指针

3.1数组指针的认识

数组是指针根据谁放在后面就是谁原则,所以数组指针是指针。那是用来干嘛的呢?我们知道

整形指针: int * pint ; 能够指向整形数据的指针。
浮点型指针: float * pf ; 能够指向浮点型数据的指针。
那数组指针应该是:int (*p)[10]能够指向数组的指针。
在此之前我们先判断一下以下代码:
int main()
{
  int *p1[10];
  int (*p2)[10];
   //p1, p2分别是什么?
  return 0;
}

我们要记得 [ ] 的优先级要高于 号的,所以p1是先和 [10]结合,类型是int*,意思就是p1是指针数组。

p2是先加上()来保证p2先和*结合。p先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p2是一个 指针,指向一个数组,叫数组指针。

3.2数组名和&数组名的区别

因为了解数组指针之前,能区分数组名和&数组名还是很有必要的。

对于下面的数组:
int arr[10];
arr &arr 分别是啥? 我们知道arr 是数组名,数组名表示数组首元素的地址。 那&arr 数组名到底是啥?
#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}
运行结果如下:

可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗?

我们再看一下以下代码:

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

运行结果如下:

首先,我们要对数组名理解:
数组名是数组首元素的地址
有2个例外:
1. sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节。
2. &数组名,这里的数组名表示整个数组, &数组名取出的是整个数组的地址。
除此之外,所有的地方的数组名都是数组首元素的地址。

所以根据上面的代码我们发现,其实 &arr arr ,虽然值是一样的,但是意义应该不一样,实际上&arr 表示的是整个 数组的地址 ,而不是数组首元素的地址。本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型,数组的地址+1 ,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是 40个字节.

3.3数组指针的使用

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    return 0;
}

一般呢,数组指针更多是应用在二维数组传参。

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

运行代码如下:

二维数组在传参时,函数既可以接收二维数组,也可以接收数组指针,因为二维数组的数组名时首元素地址,首元素不是一个变量,而是一个一维数组,所以可以用数组指针去接收。

如代码:

#include <stdio.h>

int main()
{
        int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
        Print(arr, 3, 5);
        
        return 0;
    
}

 3.4数组参数,指针参数

3.5一维数组传参

定义了一个数组和函数,在调用函数时放入数组名,函数接收时可以通过数组和指针的方式接收。因为数组在内存中时连续存放的,传入数组名,就可以知道首元素地址,再用指针接收首元素地址,就可以知道整个数组了,用数组接收数组名本质上也是指针接收

用一个简单的例子:

    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[10] = { 0 };
		test(arr);
		test2(arr2);
	}

按顺序解答:

main函数调用test函数传递了arr数组名。

    void test(int arr[])//ok?  arr是数组,可以用数组接收。ok
    {} 

  
    void test(int arr[10])//ok? 同理,arr是数组,可以用数组接收。ok
    {}


    void test(int* arr)//ok?  首元素地址可以用指针接收。ok

    {}

main函数调用test2函数传递了arr2数组名。

     void test2(int* arr[20])//ok? arr2是指针数组,可以用指针数组接收。ok的
    {}


    void test2(int** arr)//ok? arr2首元素是一级指针,必须用二级指针接收。ok的
    {}

3.6二维数组传参

二维数组传参时,实际上传递的时首元素的地址,首元素素是一个一维数组。用数组指针去接收一维数组,也可以用二维数组接收(要记得本质上也是用指针接收)

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}
void test ( int arr [ 3 ][ 5 ]) // 传过去的一维数组地址,接收也用可以二维数组接收。ok的
{}  
void test ( int arr [][]) // 二维数组可以省略行,但是不能省略列。不ok
{}
void test ( int arr [][ 5 ]) // 跟上面同理,ok的
{}
void test ( int * arr ) // 传过来的是一维数组的地址,所以要用数组指针接收,不ok
{}
void test ( int* arr [ 5 ]) //别被迷惑了兄弟们,这是指针数组,不是数组指针,不ok
{}
void test ( int ( * arr )[ 5 ]) //确认是数组指针,ok的
{}
void test ( int ** arr ) //二级指针能接收的是一级指针,不能直接接收地址/不ok
{}

3.7一级指针传参

当函数的参数是一级指针时,调用时能传入什么样的参数?

1.单一变量地址,一级指针接收的是地址没什么问题。

void my_printf(char* str)
{
	printf("%c\n", *str);
}


int main()
{
	char pc = 'K';
	my_printf(&pc);

	return 0;
}

2.数组名。因为数组名就是首元素地址;


void my_printf(char* str)
{
    printf("%s\n", str);
}
int main()
{
    char arr[] = "CSDN";
    my_printf(arr);
    return 0;
}

3.一级指针。用一个变量去接收数组名的地址,此时变量为一级指针,所以一级指针传参,一级指针接收。


void my_printf(char* str)
{
    printf("%c\n", *str);
}
int main()
{
    char cp = 'J';
    char* cpp = &cp;
    my_printf(cpp);
    return 0;
}

3.8二级指针传参

1.一级指针的地址。

void my_printf(char** str)
{
	printf("%c\n", **str);
}


int main()
{
	char pc = 'A';
	char* pcc = &pc;
	my_printf(&pcc);

	return 0;
}

2.二级指针。

void my_printf(char** str)
{
	printf("%c\n", **str);
}


int main()
{
	char pc = 'B';
	char* pcc = &pc;
	char** pccc = &pcc;
	my_printf(pccc);

	return 0;
}

3.指针数组。

void my_printf(char** str)
{
	printf("%s\n", *str);
}


int main()
{
	
	char* arr[] = {"abcdef"};
	
	my_printf(arr);

	return 0;
}

好了,今天的内容就到这里了,如果能够帮助到看官,就希望看官给个三连支持一下吧!!!、

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

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

相关文章

如何让Stable Diffusion正确画手(1)-通过embedding模型优化图片质量

都说AI画手画不好手&#xff0c; 看这些是我用stable diffusion生成的图片&#xff0c;小姐姐都很漂亮&#xff0c;但手都千奇百怪&#xff0c;破坏了图片的美感。 其实只需要一个提示词&#xff0c;就能生成正确的手部&#xff0c;看这是我重新生成的效果&#xff0c;每一个小…

【leetcode】面试题 02.01. 移除重复节点 (python + 链表)

题目链接&#xff1a;[leetcode] 面试题 02.01. 移除重复节点 # Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val x # self.next Noneclass Solution(object):def removeDuplicateNodes(self, he…

MySQL为什么采用B+树作为索引底层数据结构?

索引就像一本书的目录&#xff0c;通过索引可以快速找到我们想要找的内容。那么什么样的数据结构可以用来实现索引呢&#xff1f;我们可能会想到&#xff1a;二叉查找树&#xff0c;平衡搜索树&#xff0c;或者是B树等等一系列的数据结构&#xff0c;那么为什么MySQL最终选择了…

尚硅谷Docker实战教程-笔记12【高级篇,Docker-compose容器编排】

尚硅谷大数据技术-教程-学习路线-笔记汇总表【课程资料下载】视频地址&#xff1a;尚硅谷Docker实战教程&#xff08;docker教程天花板&#xff09;_哔哩哔哩_bilibili 尚硅谷Docker实战教程-笔记01【基础篇&#xff0c;Docker理念简介、官网介绍、平台入门图解、平台架构图解】…

一篇文章搞懂Libevent网络库的原理与应用

1. Libevent介绍 Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库&#xff0c;主要有以下几个亮点&#xff1a; > - 事件驱动&#xff08; event-driven&#xff09;&#xff0c;高性能; > - 轻量级&#xff0c;专注于网络&#xff1b; > - 源代码相当…

前端(五)——从 Vue.js 到 UniApp:开启一次全新的跨平台开发之旅

&#x1f642;博主&#xff1a;小猫娃来啦 &#x1f642;文章核心&#xff1a;从 Vue.js 到 UniApp&#xff1a;开启一次全新的跨平台开发之旅 文章目录 UniApp和vue.js什么是UniApp&#xff1f;UniApp的写法什么是vue.js&#xff1f;UniApp与vue.js是什么关系&#xff1f; 为什…

Python+Appium+Pytest自动化测试-参数化设置

来自APP Android端自动化测试初学者的笔记&#xff0c;写的不对的地方大家多多指教哦。&#xff08;所有内容均以微博V10.11.2版本作为例子&#xff09; 在自动化测试用例执行过程中&#xff0c;经常出现执行相同的用例&#xff0c;但传入不同的参数&#xff0c;导致我们需要重…

【Redis基础】快速入门

一、初识Redis 1. 认识NoSQL 2. 认识Redis Redis诞生于2009年&#xff0c;全称是Remote Dictionary Server&#xff08;远程词典服务器&#xff09;&#xff0c;是一个基于内存的键值型NoSQL数据库特征 &#xff08;1&#xff09;键值&#xff08;key-value&#xff09;型&am…

测试员如何突破自我的瓶颈?我有几点看法

前阵子我自己也对如何“突破瓶颈”思考过&#xff0c;我觉得“突破瓶颈”、“弥补短板”等等都大同小异&#xff0c;从古至今就是测试员们津津乐道的话题。我也对自己该如何“突破瓶颈”总结了几点&#xff0c;跟大家分享下&#xff1a; 1、“常立志、立长志”。“立志”就是目…

Vue脚手架使用【快速入门】

一、使用vue脚手架创建工程 在黑窗口中输入vue ui命令 再更改完路径地址后需要按回车 二、vue工程中安装elementui 第一种可以在黑窗口输入命令安装 npm install -s element-ui第二种使用图形化安装 三、 在vue工程中安装axios 第一种可以在黑窗口输入命令安装 npm inst…

ECMAScript6之一

目录 一、介绍 二、新特性 2.1 let 和 const 命令 2.2 es6的模板字符串 2.3 增强的函数 2.4 扩展的字符串、对象、数组功能 2.5 解构赋值 2.6 Symbol 2.7 Map 和 Set 2.8 迭代器和生成器 2.9 Promise对象 2.10 Proxy对象 2.11 async的用法 2.22 类class 2.23 模块…

linux内核中kmalloc与vmalloc

kmalloc 和 vmalloc 是 Linux 内核中的两种内存分配方法&#xff0c;它们都用于为内核分配内存&#xff0c;但它们在使用和管理内存方面存在一些重要差异。下面我们详细讨论这两种内存分配方法的异同。 相同点&#xff1a; 都是内核空间的内存分配方法。都可以用于动态分配内…

anaconda目录下的pkgs文件夹很大,可以删除吗?

pkgs这个目录占用了6GB的硬盘空间。 其实里面是conda安装第三方包的时候保存在本地的下载文件&#xff0c;大部分是可以删除的。 只是删除后&#xff0c;后续你需要创建虚拟环境的时候或者在虚拟环境下pip安装第三方库的时候&#xff0c;会从网络去下载&#xff0c;没法直接从…

Jmeter的常用设置(一)

文章目录 前言一、Jmeter设置中文 方法一&#xff08;临时改为中文&#xff09;方法二&#xff08;永久改成中文&#xff09;二、启动Jmeter的两种方式 方法一&#xff08;直接启动&#xff0c;不打开cmd窗口&#xff09;方法二&#xff08;带有cmd窗口的启动&#xff09;三、调…

【xxl-job】本地部署并接入xxl-job到项目中

本地部署并接入xxl-job到项目中 一、xxl-job简介 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 什么是分布式任务调度 通常任务调度的程序是集成在应用…

SparkCoreDAG

DAG有向无环图 倒推 故推导程序的执行计划时&#xff0c;先看代码有几个action算子&#xff0c;从action倒推 一个action会产生一个JOB&#xff08;DAG&#xff09;&#xff08;即一个应用程序内的子任务&#xff09; 一个action一个Job一个DAG 一个application里面可以有多…

Latex:画图识别符号

http://detexify.kirelabs.org/classify.html

RDMA RoCev2 CM建链和Socket建链测试

前言 RDMA在高性能计算&#xff0c;AI大模型训练中发挥着重要的作用。 主流支持RDMA的协议有IB、RoCev1、RoCev2、iWARP。 其中RoCev2是应用最广泛的协议&#xff0c;因为其RDMA over UDP/IP&#xff0c;不依赖昂贵的IB网络设备&#xff0c;同时支持路由&#xff0c;性能上也…

Azure Kinect 之 Note(一)

Azure Kinect Azure Kinect DK 是一款开发人员工具包&#xff0c;配有先进的AI 传感器&#xff0c;提供复杂的计算机视觉和语音模型。 Kinect 将深度传感器、空间麦克风阵列与视频摄像头和方向传感器整合成一体式的小型设备&#xff0c;提供多种模式、选项和软件开发工具包(S…

Web开发模式

Web开发介绍 1 什么是web开发 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 所以Web开发说白了&#xff0c;就是开发网站的&#xff0c;例如下图所示的网站&#xff1a;淘宝&#xff0c;京东等等 那么我们…