C++<第九篇>:指针
系统的内存就像是带有编号的小房间,如果想要使用内存就需要得到房间号,这个房间号 就是地址。
通过地址可以找到指定的内存单元。
在C++语言中,有专门用来存放内存单元地址的变量类型,它就是指针类型。
指针是一种数据类型,通常所说的指针就是指针变量。
指针和地址其实是一个意思,所以指针就是地址,地址就是指针。
一、指针的声明
声明指针的一般形式如下:
数据类型标识 *指针变量名
如:
int *p;
float *p;
char *p;
二、指针的赋值
初始化时赋值:
int i = 100;
int* p = &i;
cout << "i的地址:" << p << endl;
先声明后赋值:
int i = 100;
int* p;
p = &i;
cout << "i的地址:" << p << endl;
输出的结果是:
i的地址:00000018046FFC04
符号&(取址符)的作用是:获取变量的地址;
三、指针运算
指针变量可以++(p++),也可以--(p--),可以将指针指向上一个或者下一个内存地址。
先看下代码:
int i = 100;
int* p;
p = &i;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
输出结果是:
i的地址:000000AFDC8FF9A4
i的地址:000000AFDC8FF9A8
i的地址:000000AFDC8FF9AC
i的地址:000000AFDC8FF9B0
i的地址:000000AFDC8FF9B4
当前指针的类型是int类型,int占用4个字节,所以地址和地址之间的跨度是4个字节。
再看下代码:
int16_t i = 6;
int16_t * p;
p = &i;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
p++;
cout << "i的地址:" << p << endl;
我们将int换成了int16_t,int16_t占2个字节。输出结果是:
i的地址:000000D0F66FF9B4
i的地址:000000D0F66FF9B6
i的地址:000000D0F66FF9B8
i的地址:000000D0F66FF9BA
i的地址:000000D0F66FF9BC
可以看到,地址和地址之间的跨度变成了2个字节。
所以,可以使用自增和自减运算符将指针指向下(上)一个地址。
四、指向空的指针和空类型指针
(1)指向空的指针
int * p;
如上代码的指针p没有被初始化,没有被初始化的指针都不可以使用,一旦使用没有初始化的指针程序将崩溃,这是比较危险的。
有时候,我们宁愿先将指针指向空:
int * p = NULL;
使用指向空的指针程序不会崩溃。
如果要输出p,p的输出结果是:
0000000000000000
(2)空类型的指针
void* PI;
使用void修饰数据类型的指针称为空类型的指针,空类型的指针可以被任意类型接受,如:
int i = 6;
void* PI = &i;
有必要的话,可以使用强制转换:
(int *)PI
五、指针与数组
指针可以指向数组
int a[] = {1, 2, 3, 4, 5};
int* p = a; // 指向第一个元素
cout << p << endl;
cout << *p << endl;
如上代码,指针变量p指向了数组a,a默认是第一个元素的地址。
那么如何指向第二个元素呢?
int* p = a + 1;
或者
int* p = a;
p++;
由于数组是一个连续的空间,所以以上的写法成立。
所以,指针可以遍历数组,代码如下:
int a[] = {1, 2, 3, 4, 5};
int* p = a;
int len = sizeof(a) / sizeof(int);
for (size_t i = 0; i < len; i++)
{
cout << *p++ << endl;
}
二维数组也是一样,也可以使用指针遍历。
指针可以遍历字符串,代码如下:
char str[] = "hello word"; // 声明并初始化字符串
char* p = str; // 将指针指向字符串str
while (*p != '\0')
{
cout << *p++;
}
cout << endl;
字符串的结尾必然是字符“\0”,所以可以利用这个特性遍历字符串。
指针可以拼接字符串,代码如下:
char str1[20] = "my name is ";
char str2[] = "zhangsan";
char* p1 = str1;
char* p2 = str2;
// 将指针指向str1的末尾
while (*p1 != '\0')
{
p1++;
}
// 将p2拼接到p2后面
while (*p2 != '\0')
{
*p1++ = *p2++;
}
*p1 = '\0';
cout << str1 << endl;
六、指针在函数中的使用
先看下代码,实现数据交换的功能:
#include <iostream>
#include <string>
using namespace std;
void swap(int a, int b);
int main()
{
int x = 3;
int y = 4;
swap(x, y);
cout << "x=" << x << endl;
cout << "y=" << y << endl;
return 0;
}
void swap(int a, int b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
输出结果是:
x=3
y=4
需要在注意的是:swap方法必须声明,否则默认调用xkeycheck.h中的swap方法;
依据输出结果可以看出,交换功能没有实现,因为当x,y做为实参传递给swap时就会产生x、y的副本,swap的方法实现只是x、y副本的交换;
我们可以将交换的代码放在main方法中,这样就不会产生副本:
int x = 3;
int y = 4;
int tmp;
tmp = x;
x = y;
y = tmp;
cout << "x=" << x << endl;
cout << "y=" << y << endl;
但是,数据的交换属于一种功能,从代码优化的角度上来说,我们应该将数据交换功能的代码单独提取出来用一个方法来封装。
我们可以使用指针的方式实现数据的交换:
#include <iostream>
#include <string>
using namespace std;
void swap(int* a, int* b);
int main()
{
int x = 3;
int y = 4;
swap(&x, &y);
cout << "x=" << x << endl;
cout << "y=" << y << endl;
return 0;
}
void swap(int* a, int* b) {
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
输出结果是:
x=4
y=3
依据输出结果得出结论,使用指针做为参数的方法,可以真正实现数据的交换。
七、引用
我们可以使用引用的方式实现数据的交换。
在xkeycheck.h中,有一个swap方法:
_CONSTEXPR20 void swap(_Ty& _Left, _Ty& _Right) noexcept(
is_nothrow_move_constructible_v<_Ty>&& is_nothrow_move_assignable_v<_Ty>) {
_Ty _Tmp = _STD move(_Left);
_Left = _STD move(_Right);
_Right = _STD move(_Tmp);
}
这个函数的形式参数变量使用“&”做为变量的引用。那么我们完全可以用引用的方式写一个数据交换的功能,代码如下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
int x = 3;
int y = 4;
swap(x, y);
cout << "x=" << x << endl;
cout << "y=" << y << endl;
return 0;
}
void swap(int& a, int& b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
八、指向函数的指针
代码如下:
#include <iostream>
#include <string>
using namespace std;
int sum(int a, int b);
int main()
{
int x = 3;
int y = 4;
int(*p)(int, int) = sum;
int s = (*p)(x, y);
cout << "s=" << s << endl;
return 0;
}
int sum(int a, int b) {
return a + b;
}
int(*p)(int, int) = sum将指针指向函数sum,(int, int)做为函数sum的形式参数,(*p)(x, y)将值传入函数,s是函数sum的返回值。
九、数组指针和指针数组
(1)数组指针
int a[][2] = {1,2,3,4};
int(*p)[2];
p = a;
a是一个二维数组,指针p就是a的行,所以数组指针也称指向一维数组的指针,亦称行指针。
(2)指针数组
指针数组的声明形式是:
int *a[];
指针数组中的值都是地址。
演示代码如下:
int x = 3;
int y = 4;
int *a[] = {&x, &y};
for (size_t i = 0; i < 2; i++)
{
cout << a[i] << endl;
}
输出结果是:
00000025E70FFAA4
00000025E70FFAC4
指针数组中每个元素占用8个字节的内存空间。
如果想要输出具体的值,代码如下:
int x = 3;
int y = 4;
int *a[] = {&x, &y};
for (size_t i = 0; i < 2; i++)
{
cout << *a[i] << endl;
}
a[i]存储的是一个指针(地址),所以只要将a[i]改成 *a[i]即可。
指针数组的元素可以直接赋值为字符串:
const char* a[] = {"hello", "word"};
for (size_t i = 0; i < 2; i++)
{
cout << a[i] << endl;
}
a[0]就是"hello",a[1]就是"word"。
使用*a[i]可以操作各自元素的字符串。
[本章完...]