关于在c++中使用数组名作为函数参数,或者使用数组名的地址作为函数参数问题的一些研究

前言

使用数组名作为函数参数,或者使用数组名的地址作为函数参数,常常出现于对于字符串的读入问题之中。

常有以下两种写法:

  1. 这是使用数组名作为函数参数
#include<cstdio>
char s[100];
int main() {
	scanf("%s",s);
}

在编译时不会出现报错和警告。

2.这是使用数组名的地址作为函数参数

#include<cstdio>
char s[100];
int main() {
	scanf("%s",&s);
}

在编译的时候不会报错,默认不会有警告。但是编译选项中开启-Wall(显示全部警告)就会出现警告。

在这里插入图片描述

警告的原因是,scanf函数的%s选项期望获得一个字符类型的地址,作为读入字符串的起始地址。而&s事实上是一个字符数组类型的地址,而不是字符类型地址。

-Wall编译选项会检查scanf对应的不定参数列表中提供的类型是否正确,因此会警告。不开启-Wall选项则不会检查是否正确,则不会警告。

区别

然而这样的写法虽然会引起编译的警告,但并不会导致编译错误,而且也不会引发程序运行的错误。

准确来说,在大部分实现上,二者达到的目的是一样的。
尽管在标准中,并不确保scanf提供参数类型错误时,也会得到正确的结果,但是由于实现的关系,往往能得到正确的结果。

但是这两种方法仍然是有微小的区别的,在我们将s[0]作为读入的第一个字符的时候,两者没有区别,但假如我们将s[1]作为读入的第一个字符,那它们将会出现不同的结果。

s[0]作为第一个字符

第一种写法

#include<cstdio>
char s[100];
int main() {
	scanf("%s",s);
	printf("%s",s);
}

运行结果正确:

在这里插入图片描述

第二种写法

#include<cstdio>
char s[100];
int main() {
	scanf("%s",&s);
	printf("%s",s);
}

运行结果正确:

在这里插入图片描述

以s[1]作为第一个字符

第一种写法

#include<cstdio>
char s[100];
int main() {
	scanf("%s",s+1);
	printf("%s",s+1);
}

运行结果正确:

在这里插入图片描述

第二种写法

#include<cstdio>
char s[100];
int main() {
	scanf("%s",&s+1);
	printf("%s",s+1);
}

运行结果错误:
在这里插入图片描述

这是实际上是发生了访问越界,可以放大一下访问倍数,就会报运行错误:

#include<cstdio>
char s[2000];
int main() {
	scanf("%s",&s+20);
	printf("%s",s+20);
}

运行错误:
在这里插入图片描述

具体区别

对于这个字符数组:

char s[10]{};

首先我们知道,s和&s在数值上确实是相等的,但是其指向的类型并不相同。
但是我们知道s+1与&s+1在数值上不一定是相等的:
可以用这个程序对比一下:

#include<iostream>
using namespace std;
int main() {
	char s[10]{};
	auto p=&s;
	cout<<p<<endl;
	cout<<p+1<<endl;
	
	cout<<endl;
	
	cout<<static_cast<void*>(s)<<endl;
	cout<<static_cast<void*>(s+1)<<endl;
	
	这里要用关键字static_cast将s的类型从char*转化为void*,是因为cout默认会将char*按照字符串格式输出,而不会选择输出一个地址
	这里不使用c风格强制类型转换主要是出于习惯
}

我们发现,s+1与s相差了1,而&s+1与&s却相差了10:

在这里插入图片描述

原因

要弄清楚这个问题的原因,就需要先了解清楚s和&s有什么区别。也就是数组名和数组名的地址有什么区别。

那么首先我们要知道,将普通变量+1后,其数值会增加1。但是将指针变量+1后,增加的值等于其指向的类型占用的字节数。
如果我们认为int是4个字节,那么也就是说:

int a[]={0,1};
int* p=a[0];
p++; 此时p数值增加了4,指向了a[1]的位置
此时 *p=1

同理,如果是char类型指针,那么+1后,其数值只会增加1,因为char类型只占用一个字节。

其次,我们需要知道计算机程序在运行中必须要跟踪的数据的三个信息:

  1. 变量被存储在哪里
  2. 变量的值是多少
  3. 变量的类型是什么

我们知道,数组名在C++中通常被看做是指向数组第一个元素的指针,也就是说,s被看做是一个指向s[0]位置的char类型指针,也就是char*。
(当然这个说法并不准确,事实上,s的类型在程序中被跟踪为一个“长度为10的字符数组”,但是s+1则只被识别为“指向s[1]位置的字符指针”)

使用typeid方法获得的类型信息:

这里的"A"表示“array”,是数组。
“10”表示数组长度。
“P”表示一个指针。
“c”表示“char”。
在这里插入图片描述

我们将s看作是指向数组第一个元素的指针,因此s+1比s增加了一个字节,也就是一个char类型的长度。

我们同样可以打出&s的类型:
在这里插入图片描述
这表明编译器认为&s是一个指向长度为10的字符数组的指针,因此&s+1与&s之间相差了10个字节。
指向数组的指针,这个类型十分复杂,以至于用常规方法很难只用一条语句就把它声明出来。
我想到四个方法可以把它声明出来:

  1. 可以使用decltype方法自动推导类型:
decltype(s) *p=&s;
  1. 可以使用auto方法自动推导类型:
auto p=&s;
  1. 可以使用多条语句来声明此类型
	typedef char T[10]; 先用typedef语句声明一个T类型,T类型是一个长度为10的字符数组
	T* p=&s; 			再声明一个T类型指针
  1. 可以用这个方法声明一个指向数组的指针:
char(*p)[10]=&s;

可以看到,p本身是一个数组指针。因此p++每次会增加10。

  1. 可以使用using方法来声明此类型,就像使用typedef方法一样。
using T=char[10];
T*p=&s;

后记

于是皆大欢喜。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值