自己研究一下数组,请复制到IDE中看
#include <iostream>
using namespace std;
int main()
{
char ch = 'H';
char* pch = &ch;
//一个变量和一个变量的指针,很自然,完全可以理解
char a1[5] = {'H','e','l','l','o'};
char* pa1 = a1;
//是不是感觉也很自然,其实已经不自然了,只是你没发现,下面细细讲,
//数组其实一种复合类型,他的真实结构是
struct Array
{
char a;
char b;
char c;
char d;
char e;
};
const Array a2 = {'H','e','l','l','o'};
const Array* pa2 = &a2;
/********取元素
* a1[0]<--> a2.a
* a1[1]<--> a2.b
* ...依次类推
********取地址
* &a1[0]<--> &a2.a
* &a1[1]<--> &a2.b
* ...依次类推
*/
//Array是一种我们自定义的数据类型,他的结构中包含了5个char类型元素,我们用Array类型一个变量a2
//sizeof(a2)==>输出了a2的大小,一切都很自然
//由于这些元素的类型都相同,人们觉得采用结构体这种形式定义很麻烦啊,于是就发明了数组,表示元素类型相同的一种复合结构
//想一想,如果没有数组,那么a[1000],a[10000],用结构体来定义,是不是一屏幕的定义代码
//现在我们要发明一种简写方式
//1. 模仿结构体定义别名的方式:typedef struct Array alias,于是有
typedef char myArray[5];
//此刻应该明白为什么数组不能相互赋值,因为只要元素类型和大小不同,那么他就是不同的数据类型,不过C语言真的垃圾,连个typedef都没法用法统一,C++ using就好用多了
myArray a3 = {'H','e','l','l','o'};
//这样就我们定义了我们想要的数组了,但是已经不能用'.'运算符去取里面的数据了,只能通过地址取数据了
myArray* pa3 = &a3;//typedef char myArray[];如果5不写,完全定义不了指针,因为不知道所指空间多大
//2. 如果编译器通过pa3地址来翻译要取的数据,那么一定是取一个sizeof(a3)大小的数据出来,而我们希望的是一个个字符取出来
char* p = (char*)pa3;//这样我们取到了第一个字符地址p,那么p+1就是第二字符地址...依次类推,如下所示
/*地址***取元素*****语法糖(改变外貌,但是不影响功能的语法,更方便程序员使用)
* p<--> *(p)<=> p[0]
* p+1<--> *(p+1)<=> p[1]
* ...依次类推
*/
//现在总结一下:我们知道数组一种复合数据类!型!,可以用他定义变量
//可是按照上面这样又定义,又取值,可是太麻烦了
//我们发现如果我们给数组类型的变量取地址,和给数组内部第一个元素取地址,他们是一样的
//那么可不可以这么定义一个数组char pa[5],定义一个数组,变量名叫pa,他有五个元素,每个元素都是char类型的
//类比于int a;类型是int,变量是a,地址是&a
//类型是char [5],变量名叫pa,&pa是整个数组地址,sizeof(pa)就是数组大小
//同时规定,数组名就是数组首元素的地址,那么pa[0],pa[1]...表示每一个元素
//现在明白我为什么说不自然了吧,因为只有和变量类型相同的指针类型才能指向这个类型的地址,否则就是乱指
char* pa = a1; //这句话能成立,主要是标准中规定:数组类型的变量名除了表示该数组本身,还强制规定是数组首元素的地址,此处a1就是首元素地址
char (*pb)[5] = &a1;//此处a1是数组类型的变量,这是获取整个数组的地址,必须是数组类型指针才能指向他
//不同类型的指针,访问的数据类型不同,本质是一次访问的内存大小不同
return 0;
}
字符串和字符数组的区别
#include <iostream>
using namespace std;
int main()
{
const char ch = 'H';
const char* pch = &ch;
const char a1[5] = {'H','e','l','l','o'};
const char* pa1 = a1;
//内存中的样子[H][e][l][l][o],pa1-->[H]
//关于字符串"Hello",首先要明白C语言中没有string这种类型
//字符串在内存中的样子也是[H][e][l][l][o][\0],和数组的存储方式惊人的类似,只是多了\0,表示字符串结束
const char* pa2 = "Hello"; //const表示所指的这个空间内容不可变动
//上面这句话编译器做了什么?
//1.根据"Hello"的字符串长度,分配一段char类型的空间
//2.将"Hello"中的字符一个一个复制到以上的空间中
//3.在以上空间的末尾添加数字0作为结束标志
//4.将字符串的首地址返回(char*类型)
//为什么我们可以const char*类型指向一个字符串?
//因为存储结构相似,C语言中把字符串就当作是尾元素是'\0'的字符数组,
//"Hello"即是数组名,也是首元素地址,sizeof ("hello"),"Hello"[0]或是*("Hello" + 0),表示第一个元素
cout << &("hello") << endl; //输出字符串地址
cout << (void *)"hello" <<endl; //输出字符串首元素地址
//我们用const char*指向他,只是表示我们想用char类型来读取他所指向内存中的内容
char a[] = "hello"; //[H][e][l][l][o][\0]
cout << sizeof(a) << sizeof("hello") << endl;
cout << a[0] << "hello"[0] << *("hello"+0) << endl;
return 0;
}
为什么数组之间不能相互赋值
#include <iostream>
using namespace std;
using Array = int[3];
void Traversal1(Array x);
void Traversal2(Array* x);
int main(void)
{
Array a = {1,2,3};
//Array b = a;//错误,数组必须要用初始器列表初始化
//b = a;//错误,数组名不可相互赋值
//Traversal1(a);
Traversal2(&a);
}
void Traversal1(Array x)//错误,给数组类型参数传值,会自动转换为数组元素类型指针
{
cout << sizeof(x) <<endl;
//warning: sizeof on array function parameter will return size of 'int *' instead of 'Array' (aka 'int[3]')
}
void Traversal2(Array* x)//正确,成功输出数组大小
{
cout << x[0]<< sizeof(*x) <<endl;
}
问题1:为什么Array可以定义数组变量,但是不能变量之间不能相互赋值,太违反我们接受的变量概念了?
首先人家这么规定肯定是有他的道理的,假设b = a是对的,那么Traversal1()函数的参数x就能接受赋值,
这个推论应该没有问题,我们知道发明数组的目的就是为了存一组可能比较大但是类型相同的数组,
并且存储空间连续,方便管理,如果实参数组a能对形参数组x复制那么是不是意味着又要一块这么大的内存,
*1.太浪费内存,有干爆栈的危险*,而且还要拷贝数据进去,假使再反复的调用这个函数,
*2.那程序运行效率将大幅降低*,另外有人可能会将数组作为返回值,函数的返回值通常是先存储临时变量中,
这又会托跨效率,更要命的是很多CPU临时变量是放在寄存器中的,直接日爆寄存器了
综上所述
对于数组这种大类型而言,传指针更靠谱,如果对元素操作,直接参数类型定义成元素类型指针,
如果对整个数组操作,直接定义为数组指针类型变量,反正就是能类型定义为数组类型变量,
为了达到这个目的,直接禁止赋值最好