基础不牢,地动山摇。当初学C语言的时候,指针,数组等概念一直分不清楚,十分混乱。后期字符串与字符数组的出现更是云里雾里。现在学了C++,加上一些C++11后的特性数组,对这玩意的用法更加迷惑,时而&arr
时而&arr[0]
。今日来做个了结。
字符与ASC码
我们知道在计算机内部,所有信息最终都是一个二进制值,这称为数字数据。而字符数据通常称为文本,如记事本文件,word文件等。计算机使用多种类型的编码方式来展示字符数据。其中之一的编码方式为ASC码,使用8个bit位来表示一个字符。所以,在计算机设备上显示的文档是经过ASC,Unicode或UTF-8编码后的一串二进制数字。我们可以通过以下程序来观察字符A
的二进制:
1 |
|
字符数组
在C语言中没有专门的字符串变量,通常用一个字符数组来存放一个字符串,且以’\0’作为串的结束符。在C语言中,它有多种初始化方法:
1 | char c[]{'a', 'b', 'c', 'c', '\0'}; |
这里需要注意的是,C语言中数组名表示该数组的首地址,整个数组是以首地址开头的一块连续的内存单元。也就是,对于字符数组c
而言,若从键盘读入,scanf("%s", &c)
是错误的,不应该取地址:scanf("%s", c)
。在执行输出函数时,按数组c
找到首地址,然后逐个输出数组中各个字符直到遇到字符串终止标志\0
为止。自己不添加终止符时,编译器会帮你添加,但最好自己添加。
我们可以使用格式化字符串%s
整体输出字符数组,这样不会尴尬的输出数组的首地址。但对于非字符型数组而言,输出的就是数组首地址了:
1 |
|
整型数组转字符数组整体输出
看了上面的代码,既然输出整型数组的数组名时,输出的是地址。总所周知数组名是指针,那么把int*
的指针强制改为char*
的指针会出现什么情况?这里需要注意的是:int
占4个字节,char
占一个字节,强制类型转化时,char
类型的开头会是0
,也就是C语言中字符数组的结尾。所以以下程序是没有反应的:
1 |
|
看下内存就懂了:
此时突发奇想,如果一定要按照%s
格式的方法输出整型数组里面的全部内容,该如何实现呢?先补充点额外知识,假设有一个数据单元,长度是$n$个bit,那么两个数据单元就是$2n$个比特。如果让这两个数据单元内的数值一致,假设想要两个数据单元里面存储的都是$x$,那么写入的数据应该是:$x\times 2^n+x$。
如下图所示,长度为4的两个数据单元写入170时,两个数据单元内都是10。
这样,使用uint_16
(两个字节)类型转化为char
类型是,就容易控制些了。因为256*65+65=16705
,来个一次性输出整型数组内容的操作:
1 |
|
这样,就不会在有0x0
的存在打断输出了,且,整型的65会转换为char
类型的A
输出。
数组与指针
指针
我们接着往下看,虽然初学指针令人头疼,但掌握指针的确会对程序以及内存有更好的理解。内存可以看成一系列连续编址的单元,而指针是能存放地址的一组单元。假设指针p
指向数据c
,那么就p = &c
,可以画图为:
可以使用一元运算符*
来间接寻址,找到地址存储的内从,可以通过简单的程序来观察指针如何使用:
1 |
|
在复杂一点点,可以通过传递参数的地址,来避免临时变量形参带来的问题,以交换数据为例:
1 |
|
数组
假设此时定义的数组为int a[10]
,指针为int* pa = &a[0]
,那么指针pa
指向了数组a
的第0个元素,也就是说,pa
的值为数组元素a[0]
的地址。因为数组名所代表的就是数组最开始的一个元素的地址,所以pa = &a[0]
和pa = a
等价。
- 数组引用的形式
a[i]
和*(a + i)
也是一样的,表示取数组a
的第i
个元素 &a[i]
和a + i
的含义也是相同的,表示数组a
的第i
个元素的地址pa[i]
和*(pa + i)
也是一样的,表示取数组a
的第i
个元素- 两者之间的不同之处在于,指针是一个变量,所以
pa++
是可以的,但a++
是不行的,因为数组名不是变量。
所以:如果在调用子函数时传递数组名,实际传递的是一个地址,因此子函数的形参应该为指针类型,假设写一个求字符串长度的函数:
1 |
|
C++ 风格的数组
在使用C++的array
数组时需要注意的是,假设生命的数组为:std::array a{1, 2, 3, 4, 5};
,那么&a
表示取出a这个数组对象的地址,&a[0]
才是取出存储数组位置的首地址。看程序:
1 |
|
C++引用
引用相当于给变量起了个外号,引用附着在存在的变量上。对引用做的读写操作,作用在原来变量上,所以引用在定义的时候就必须被初始化,否则引用不存在。
当引用作为参数传递时,在被调函数中,改变引用参数值,会改变实际参数的值。可以查看上文的交换变量的函数。
1 | void swap_refer(int& tmp1, int& tmp2){ |
如果想要使swap_refer(a, 4)
不报错,且函数的参数仍然是引用,这时需要引入常量:
1 | int add(int& tmp1, const int& tmp2){ |
使用引用或指针的优势是:避免传参过程中的变量拷贝带来额外的开销。