C语言之指针进阶篇(1)

news2025/2/26 18:46:41

目录​​​​​​​

引言

字符指针

指针数组

数组指针

数组指针的定义

&数组名vs数组名

数组指针的使用 

一维数组使用 

二维数组使用

一维数组传参

二维数组传参

总结 

数组参数

一维数组传参

二维数组传参

指针参数

一级指针传参

二级指针传参


引言

今天学习指针的进阶版,那还是回顾一下我们前面学习过的指针知识。

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

字符指针

%c   打印字符
%s   打印字符串

在指针初阶中我们学习过指针的类型,我们知道有一种指针类型为字符指针char*

#include<stdio.h>
int main()
{
	char c = 'x';
	char ch = "abcdef";
	char* pc = &ch;//存放字符串全部的地址
	char* p = &c;//存放字符的地址
	printf("%c", *p);
	return 0;
}

 除了上面的传统使用方法,我们还有另外一种使用方法:

#include<stdio.h>
int main()
{
	char* pc = "abcdef";
	printf("%c\n", "abcdef"[0]);
	printf("%s\n", pc);
	printf("%c", *pc);
	return 0;
}

  •  char * pc = "abcdef"本质是把字符串"abcdef"首地址放到了字符指针变量pc中。
  • "abcdef"是表达式,值就是a的地址。
  • 一个常量字符串的首地址+%s打印=字符串所有内容(根据首地址遍历字符串所有内容)
  • "abcdef"也可以理解为数组名 == 相当于 数组的首地址被存放。
  • *pc是只能访问一个字符。

但是上面的使用存在一定的风险,什么风险呢?

"abcdef"是常量字符串表达式,"常量"是不能改变的。

 常量字符串是不能被修改的。

那怎么解决呢?

 加上const即可。

#include<stdio.h>
int main()
{
	const char* pc = "abcdef";//不会被修改了
	printf("%s\n", pc);
	*pc = "g";
	return 0;
}

练习——剑指offer 

#include <stdio.h>
int main()
{
	char str1[] = "tangsiqi";
	char str2[] = "tangsiqi";
	char* str3 = "tangsiqi";
	char* str4 = "tangsiqi";
	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");

	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是内存中不同的空间指向了同一个字符串的首地址。 

指针数组

 在前面我们学习过指针数组以及它的使用(模拟二维数组的作用)。

指针数组是指针还是数组呢?

是数组啦。是存放指针的数组。

字符数组——存放字符的数组

整型数组——存放整型的数组

指针数组——存放指针(地址)的数组,存放在数组中的元素都是指针类型的。

//例如:char * arr[5];   int * ch[6];

//arr[5]表明是数组

模拟二维数组

//使用指针数组,模拟一个二维数组
#include<stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
     //指针数组
	int* arr[] = { arr1,arr2,arr3 };
	             //0   1     2
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)//找到首元素
	{
		for (j = 0; j < 5; j++)//遍历每一个数组的元素
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

除了上面整型指针数组,还有字符指针数组。 

#include<stdio.h>
int main()
{
	char* arr[5] = { "tangsiqi","xueguodi","haha","hehe","xiaoxiao" };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

 

int * arr1[10];//整形指针数组
char * arr2[4];//一级字符指针的数组
char * *arr3[5];//二级字符指针的数组

数组指针

数组指针是指针?还是数组?

指针。

指针数组——是数组,是存放指针的数组。

数组指针——是指针,指向数组的指针。

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

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

浮点型的指针——指向浮点型的指针。

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

char b='x';
char * pb=&b;

 那么数组指针怎么写呢?

数组指针的定义

定义:数组指针是指针,指向数组的指针。 //type_t (* p) [const_n];

int arr[10];
int *p1[10];
int (*p2)[10];

 上面那个代码是指针数组,那个是数组指针?p1,p2分别代表什么?

int arr[10];
int *p1[10];//指针数组
int (*p2)[10];//数组指针
//解释:
//p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

&数组名vs数组名

//数组名 vs &数组名
// &数组名是整个数组的地址
//数组名是数组首元素的地址
//除了两个例外:
//1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
//2.&数组名,这里的数组名表示整个数组名,取出的是数组的地址。
#include<stdio.h>
int main()
{
	int arr[10];
	printf("%d\n", arr);//int*
	printf("%d\n", arr + 1);
	printf("%d\n", &arr);//这个是什么类型?
	printf("%d\n", &arr + 1);
    //指针类型决定了指针+1,到底+几个字节
	return 0;
}

根据上面代码运行结果我们发现,虽然&arr和arr值是一样的,但是意义应该是不一样的。

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

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

int*p[10]=arr    +1跳过4个字节

int(*p)[10]=&arr     +1跳过10个元素每个元素为int的数组也就是 40个字节

那应该怎样存放呢?

#include<stdio.h>
int main()
{
	int arr[10];
	int(*p)[10] = &arr;
	//p是用来存放整个数组的地址,p就是数组指针
	char* arr2[5];
	char* (*pc)[5] = &arr2;
	return 0;
}

 int (*p) [10];

//*p表明是指针

// 数组指针指向数组10个元素,每个元素的类型是int 

特别提醒: 以下写法均是错误的❌

int arr[10];
❌int (*p)[]=&arr;//不能省略
❌int [10]*p=&arr;

数组指针的使用 

一维数组使用 

一维数组的arr数组名就是首元素地址_用指针接收
int *p =arr;
一维数组的&arr取地址数组名是整个数组的地址_用数组指针接收
int (*p)[10]=&arr;
//一维数组使用
#include<stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int(*p)[5] = &arr;
//值是放到p里面,不是*p,值是&arr
//&arr是整个数组的地址
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", (*p)[i]);//p[i]❌
                    //(*&arr) ==arr——*和&可以抵消
	}
}
//一维数组传参
#include<stdio.h>
void print(int(*p)[5])
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d", (*p)[i]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	print(&arr);
	return 0;
}

 把数组arr的地址赋值给数组指针变量p,但是我们一般很少这样写代码。 

二维数组使用

二维数组的arr是首元素地址,就是第一行数组(一维数组)整个的地址
用数组指针来接受
int(*p)[][5]=arr;
//此时不需要取地址
//二维数组
#include<stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	int(*p)[5] = arr;//二维数组数组名 ==首元素 == 二维数组的第一行
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d", p[i][j]);
		}
		printf("\n");
	}
	return 0;
}
//二维数组
#include<stdio.h>
void print(int(*p)[5], int row, int col)//本质就是二维数组的首元素地址==第一行地址
//void print(int arr[3][5],int row,int col)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
}
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;
}

 上面我们指针数组涉及到一维数组传参和二维数组传参的点我们来学习下。(传arr首元素地址)

一维数组传参

//数组形式
#include<stdio.h>
void print(int arr[])
//void peintf(int arr[10])也可
{
	int i = 0;
	for (i = 0; i < 10;i++)
	{
		printf("%d ", arr[i]);//*(arr+i)
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr);
	return 0;
}
//指针形式
#include<stdio.h>
void print(int* arr)
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr);
	return 0;
}

二维数组传参

//二维数组传参
//数组形式
#include<stdio.h>
void print(int arr[3][5],int row,int col)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
}
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;
}

//指针形式
#include<stdio.h>
void print(int (*arr)[3][5], int row, int col)//✔
//void print(int (*arr)[5], int row, int col)✔
//void print(int (*arr)[][5], int row, int col)❌——四不像
//void print(int arr[][5], int row, int col)✔
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", (*arr)[i][j]);
		}
		printf("\n");
	}
}
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);
//&arr——首元素地址的地址
	return 0;
}

总结 

 学了指针数组和数组指针我们来看下下面代码的意思:

  •  arr是一个能够存放5个整型数据的数组
  • parr1是一个指针数组,数组10个元素,每个元素的类型是int*
  • parr2是一个数组指针,指向数组,指向的数组有10个元素,每个元素的类型是int
  • parr3是一个数组,数组有10个元素,存放10个元素都是数组指针,数组指针是指针,    指向的数组有5个元素,每个元素是int类型。 

首先确定是指针还是数组——看符号的优先级,变量先与那个符号结合🆗🆗

把这部分除去,看剩下的就是变量的类型了。🆗🆗

数组参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数应该如何设计?

一维数组传参

#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);//首元素_是指针的地址 
}

数组传参,参数是可以写成数组形式的,可以写成指针形式。

数组传参本质是:传递了数组首元素的地址。 

二维数组传参

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);//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 *p)
{
//
}

int main()
{
  int a=10;
  int * pa=&a;
  int arr[5];
//传参
  test(&a);//传整型变量的地址
  test(pa);//传整型的指针
  test(arr);//传整型一维数组的数组名
  return 0;
}

二级指针传参

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

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

#include<stdio.h>
void test(int **p)
{
//
}
int main()
{
   int n=10;
   int*p=&n;
   int **pp=&p;
   int* arr[5];//指针数组
//传参
   test(&p);
   test(pp);
   test(arr);
   return 0;
}

关于数组和指针以及它们传参相信大家有了深刻的理解,函数指针我们会在下篇博文讲到。🙂🙂

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎大家指正!

代码-------→【gitee:https://gitee.com/TSQXG】

联系-------→【邮箱:2784139418@qq.com】

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

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

相关文章

Jmeter对websocket进行测试

JMeterWebSocketSampler-1.0.2-SNAPSHOT.jar下载 公司使用websocket比较奇怪&#xff0c;需要带认证信息进行长连接&#xff0c;通过websocket插件是请求失败&#xff0c;如下图&#xff0c;后面通过代码实现随再打包jar包完成websocket测试 本地实现代码如下&#xff1a; pa…

总结,由于顺丰的问题,产生了电脑近期一个月死机问题集锦

由于我搬家&#xff0c;我妈搞顺丰发回家&#xff0c;但是没有检查有没有坏&#xff0c;并且我自己由于不可抗力因素&#xff0c;超过了索赔时间&#xff0c;反馈给顺丰客服&#xff0c;说超过了造成了无法索赔的情况&#xff0c;现在总结发生了损坏配件有几件&#xff0c;显卡…

Java 项目日志实例基础:Log4j

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ 介绍几个日志使用方面的基础知识。 1 Log4j 1、Log4j 介绍 Log4j&#xff08;log for java&#xff09;是 Apache 的一个开源项目&#xff0c;通过使用 Log4j&#xff0c;我…

奥威BI数据可视化工具:个性化定制,打造独特大屏

每个人都有自己独特的审美&#xff0c;因此即使是做可视化大屏&#xff0c;也有很多人希望做出不一样的报表&#xff0c;用以缓解审美疲劳的同时提高报表浏览效率。因此这也催生出了数据可视化工具的个性化可视化大屏制作需求。 奥威BI数据可视化工具&#xff1a;个性化定制&a…

nginx代理webSocket链接响应403

一、场景 使用nginx代理webSocket链接&#xff0c;nginx响应403 1、nginx访问日志响应403 [18/Aug/2023:09:56:36 0800] "GET /FS_WEB_ASS/webim_api/socket/message HTTP/1.1" 403 5 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit…

opencv-dnn

# utils_words.txt 标签文件 import osimage_types (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff")def list_images(basePath, containsNone):# return the set of files that are validreturn list_file…

机器学习|决策树:数学原理及代码解析

机器学习&#xff5c;决策树&#xff1a;数学原理及代码解析 决策树是一种常用的监督学习算法&#xff0c;适用于解决分类和回归问题。在本文中&#xff0c;我们将深入探讨决策树的数学原理&#xff0c;并提供 Python 示例代码帮助读者更好地理解和实现该算法。 决策树数学原…

大语言模型-RLHF(七)-PPO实践(Proximal Policy Optimization)原理实现代码逐行注释

从open AI 的论文可以看到&#xff0c;大语言模型的优化&#xff0c;分下面三个步骤&#xff0c;SFT&#xff0c;RM&#xff0c;PPO&#xff0c;我们跟随大神的步伐&#xff0c;来学习一下这三个步骤和代码实现&#xff0c;本章介绍PPO实践。 生活中&#xff0c;我们经常会遇到…

数字化时代,数据仓库和商业智能BI系统演进的五个阶段

数字化在逐渐成熟的同时&#xff0c;社会上也对数字化的性质有了进一步认识。当下&#xff0c;数字化除了前边提到的将复杂的信息、知识转化为可以度量的数字、数据&#xff0c;在将其转化为二进制代码&#xff0c;引入计算机内部&#xff0c;建立数据模型&#xff0c;统一进行…

Java数据库连接池原理及spring boot使用数据库连接池(HikariCP、Druid)

和线程池类似&#xff0c;数据库连接池的作用是建立一些和数据库的连接供需要连接数据库的业务使用&#xff0c;避免了每次和数据库建立、销毁连接的性能消耗&#xff0c;通过设置连接池参数可以防止建立连接过多导致服务宕机等&#xff0c;以下介绍Java中主要使用的几种数据库…

IP 地址监控工具

地址监控实用程序是一套 IP 工具&#xff0c;包括 IP 地址监控工具、流氓检测工具和 MAC 地址解析器&#xff0c;用于日常监控和管理 DNS 名称、IP和 MAC 地址。地址监控工具用于 IP监控&#xff0c;用于管理 DNS 名称、网络的 IP 和 MAC 地址&#xff0c;并跟踪 IP 地址。 IP…

基于基于springboot+vue+B2C模式的电子商务平台【源码+论文+演示视频+包运行成功】

博主介绍&#xff1a;✌csdn特邀作者、博客专家、java领域优质创作者、博客之星&#xff0c;擅长Java、微信小程序、Python、Android等技术&#xff0c;专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推…

Unity 物体的运动之跟随鼠标

你想让鼠标点击哪里&#xff0c;你的运动的对象就运动到哪里吗&#xff1f; Please follow me ! 首先&#xff0c;你要先添加一个Plane ,以及你的围墙&#xff0c;你的移动的物体 想要实现跟随鼠标移动&#xff0c;我们先创建一个脚本 using System.Collections; using Syst…

Coremail参与编制|《信创安全发展蓝皮书——系统安全分册(2023年)》

信创安全发展蓝皮书 近日&#xff0c;Coremail参与编制的《信创安全发展蓝皮书—系统安全分册&#xff08;2023年&#xff09;》重磅发布。 此次信创安全发展蓝皮书由工业和信息化部电子第五研究所联合大数据协同安全技术国家工程研究中心重磅共同发布。 本次蓝皮书涵盖信创系…

关于路由器和DNS解析的一些新理解

其实我本人对于交换机和路由器这些网络硬件是比较感兴趣的&#xff0c;也在一点一点的学习相关知识&#xff0c;每次解决一个问题&#xff0c;就让我对一些事情有新的思考。。 今天前台同事&#xff0c;的机器突然上不了网&#xff0c;&#xff0c;和领导一起去看了一波&#…

Danswer 快速指南:不到15分钟打造您的企业级开源知识问答系统

一、写在前面 至于为什么需要做企业知识库&#xff0c;知识问答检索系统&#xff0c;以及现有GPT模型在企业应用中存在哪些劣势&#xff0c;今天在这里就不再赘述了&#xff0c;前面介绍其他构建知识库案例的文章中基本上都有讲过&#xff0c;如果您有兴趣可以去翻翻历史文章来…

小样本图像分类研究综述

https://kns.cnki.net/kcms2/article/abstract?v3uoqIhG8C44YLTlOAiTRKibYlV5Vjs7ioT0BO4yQ4m_mOgeS2ml3UDKtyAQtTA0dGC-TDvW_fPi0YZxfWg8dHOnpSvOL7pVA&uniplatformNZKPT 摘 要&#xff1a; 近年来&#xff0c;借助大规模数据集和庞大的计算资源&#xff0c;以深度学习为…

UE4/5Niagara粒子特效之Niagara_Particles官方案例:1.1->1.4

目录 1.1-Simple Sprite Emitter ​编辑 发射器更新 粒子生成 粒子更新 1.2-Simple Sprite Emitter 发射器更新 粒子生成 粒子更新 渲染 1.3-Simple GPU Emitter 属性 发射器更新 粒子生成 粒子更新 1.4-Sprite Facing 发射器更新 粒子生成 粒子更新 通过对官方…

Web菜鸟入门教程 - Radis实现高性能数据库

Redis是用C语言开发的一个高性能键值对数据库&#xff0c;可用于数据缓存&#xff0c;主要用于处理大量数据的高访问负载。 也就是说&#xff0c;如果你对性能要求不高&#xff0c;不用Radis也是可以的。不过作为最自己写的程序有高要求的程序员&#xff0c;自然是要学一下的&a…

博客系统之单元测试

对博客系统进行单元测试 1、测试查找已存在的用户 测试名称 selectByUsernameTest01 测试源码 //查找用户&#xff0c;存在 Test public void selectByUsernameTest01 () { UserDao userDao new UserDao(); String ret1 userDao.selectByUsername("张三").toStr…