C/C++ 指针
1 什么是指针?
指针就是内存地址,所有的变量除了值之外都有相应的地址,使用&
运算符可以获得变量的地址.
int main(){
int a = 10;
cout << "a=" << a << endl << "a的地址为" << &a << endl;
}
输出如下:
a=10
a的地址为00E8FD08
我们可以使用type* p_name
的方式去定义一个指针,并为他赋予一个地址作为值,如下:
int main()
{
int a = 10;
cout << "a=" << a << endl << "a的地址为" << &a << endl;
int* p = &a;
int* ap;
ap = &a;
cout << "p=" << p << endl << "*p=" << *p << endl << "&p=" << &p << endl;
cout << "ap=" << ap << endl << "*ap=" << *ap << endl << "&ap=" << &ap << endl;
return 0;
}
输出:
a=10
a的地址为00B5FE14
p=00B5FE14
*p=10
&p=00B5FE08
ap=00B5FE14
*ap=10
&ap=00B5FDFC
这意味着如果两个指针所指向的变量是相同的,那么指针所储存的地址也是相同的.这意味着地址和变量一一对应,但是可以看见ap
和p
本身的地址并不相同.
值得注意的是:所有的实际数据类型,不管是整型,浮点型,字符型,还是其他的数据类型对应的指针的值的数据类型都是一样的,都是一个代表内存地址的16进制数,不同数据类型的指针仅有所指向的变量或者常量的数据类型不同.
int main()
{
int* ap;
double* bp;
float* cp;
char* dp;
cout << "ap:" << sizeof(ap) << " " << "int为" << sizeof(int) << endl;
cout << "bp:" << sizeof(bp) << " " << "double为" << sizeof(double) << endl;
cout << "cp:" << sizeof(cp) << " " << "float为" << sizeof(float) << endl;
cout << "dp:" << sizeof(dp) << " " << "cahr为" << sizeof(char) << endl;
return 0;
}
输出结果如下:
ap:4 int为4
bp:4 double为8
cp:4 float为4
dp:4 cahr为1
可以看到无论什么类型的指针,所占的内存大小都是4字节.
注:实际上指针所占用的大小与当前系统有关,如果是32位(x86)就是4字节,64位(x64)就是8字节,笔者所使用编译器为Visual Studio,设置为32位运行环境
2 如何使用指针?
指针最常用的几个操作如下
- 定义一个指针
type* p_name
int* p;
- 把变量地址赋值给指针
p_name=&var_name
p=&a;
- 访问指针变量中可用地址的值(指针的解引用)
*p_name
*p
3 空指针与野指针
3.1 空指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值是一个良好的编程习惯,即被赋予NULL作为值的指针被称为空指针
NULL为定义在标准库中的一个值为零的常量
int main()
{
int* p = NULL;
cout << p << endl;
return 0;
}
输出结果如下
00000000
在大多数系统上,不允许访问地址为0的内存区域,因为该内存区域为操作系统所保留,然而,内存地址为零有特别重要的意义,他表示该指针不指向可以访问的内存区域,但是按照惯例,如果指针包含空值,则假定他不指向认可东西
可以使用如下语句来检测是否为空指针
if(p)//如果p不为空指针,执行
if(!p)//如果p是空指针,执行
3.2 野指针
野指针指的是指向了非法的内存空间的指针,野指针无法进行操作,所以在实际编程过程中尽量避免出现野指针.
int* p =(int*)0x1332;//0x1332是一个十六进制数字,转换为int*类型
4 使用const
修饰指针
4.1 const
修饰指针
const
修饰指针被称为常量指针
int a = 100;
const int* p = &a;
我们可以对p进行任何操作,但是不可以对*p进行任何操作.
int main()
{
int a = 100;
int b = 10;
const int* pb = &b;
int* pa = &a;
cout << "a=" << a << " " << "b=" << b << endl;
cout << "*p=" << *pb << " " << "*pa=" << *pa << endl;
//*pb = 5;
*pa = 50;
cout << "a=" << a << " " << "b=" << b << endl;
cout << "*p=" << *pb << " " << "*pa=" << *pa << endl;
return 0;
}
输出如下:
a=100 b=10
*p=10 *pa=100
a=50 b=10
*p=10 *pa=50
如果将*pb=5
取消注释,则会报如下错误:
表达式必须是可修改的左值
如果想要更改p对应的值,不应该对*p做操作,可以更改p的指向或者a的值.
4.2 const
修饰常量
使用const
修饰常量被称为指针常量
int* const p = &a;
与常量指针相反,对于指针常量,我们可以修改*p,但是不可以修改p
int main()
{
int a = 100;
int b = 10;
int* const pb = &b;
int* pa = &a;
cout << "a=" << a << " " << "b=" << b << endl;
cout << "*p=" << *pb << " " << "*pa=" << *pa << endl;
*pb = 5;
*pa = 50;
//pb = &a;
cout << "a=" << a << " " << "b=" << b << endl;
cout << "*p=" << *pb << " " << "*pa=" << *pa << endl;
return 0;
}
如上程序发现*pb=5;
一句可以正确执行,但是pb=&a;
一句则无法执行.
取消注释,同样会报如下错误:
表达式必须是可修改的左值
4.3 const
修饰指针和常量
const int* const p = &a;
使用如上语句同时具备指针常量和常量指针的特点,我们既不可以对p
进行操作,也不可以对*p
进行操作.
若要修改p所对应的值,应该修改其指向的变量a
;
5 指针的运算符
5.1 算术运算符
指针的值是一个内存地址,这意味着指针同样可以进行算术运算,
指针允许有以下四种运算:++,--,+,-
.
如下:
int main()
{
int a = 100;
double b = 10.0;
char c = 'a';
int* p_int = &a;
double* p_double = &b;
char* p_char = &c;
cout << "p_int=" << (int)p_int << endl;
cout << "p_double=" << (int)p_double << endl;
cout << "p_char=" << (int)p_char << endl;
p_int++;
p_double++;
p_char++;
cout << "p_int=" << (int)p_int << endl;
cout << "p_double=" << (int)p_double << endl;
cout << "p_char=" << (int)p_char << endl;
return 0;
}
输出结果如下:
p_int=18086144
p_double=18086128
p_char=18086119
p_int=18086148
p_double=18086136
p_char=18086120
修改至十进制后可以清楚的看到,p_int
增加了4,p_double
增加了8,p_char
增加了1;
可以得到以下结论:
- 指针的每一次递增,都会指向下一个元素的地址
- 指针的每一次递增,都会指向上一个元素的地址
- 指针在递增和递减是移动的大小决定于指针的类型
- double类型的指针为8字节
- int类型的指针为4字节等.
5.2 关系运算符
指针可以使用关系运算符进行比较,常见的有==,!=,>,<
等等
int main()
{
int a = 100;
int b = 10;
int c = 50;
int* p = &a;
if (p==&a)
{
cout << "p==a" << endl;
}
else if (p==&b)
{
cout << "p==b" << endl;
}
else
{
cout << "p==c" << endl;
}
return 0;
}
输出如下:
p==a
5.3 实际应用
指针使用算术运算符和关系运算符可以对数组进行访问.
因为数组名类似于一个指针常量,我们无法对一个数组进行自增自减操作,所以我们经常使用指针代替数组.
使用指针遍历数组如下:
int main()
{
int a[5] = {9,8,7,6,5};
int* pa = a;
for (int i = 0; i < 5; i++)
{
cout << pa << " " << *pa << endl;
pa++;
}
return 0;
}
结果如下:
0079FBF8 9
0079FBFC 8
0079FC00 7
0079FC04 6
0079FC08 5
同样可以使用指针自减来访问数组:
int main()
{
int a[5] = {9,8,7,6,5};
int* pa = &a[4];
for (int i = 0; i < 5; i++)
{
cout << pa << " " << *pa << endl;
pa--;
}
return 0;
}
结果如下:
00EFF9F4 5
00EFF9F0 6
00EFF9EC 7
00EFF9E8 8
00EFF9E4 9
同样可以使用while语句,以指针使用关系运算符作为条件
int main()
{
int a[5] = { 9,8,7,6,5 };
int* pa = a;
while (pa <= &a[4])
{
cout << pa << "\t" << *pa << endl;
//'\t'是转义字符,读者可以自行查阅转义字符相关知识
pa++;
}
return 0;
}
结果如下:
00A5FA38 9
00A5FA3C 8
00A5FA40 7
00A5FA44 6
00A5FA48 5
6 指针指向指针
指针同样可以指向指针,即指针链,比如:指针a
指向指针b
,指针b
指向值c
;
如下:
int main()
{
int c = 10;
int* b = &c;
int** a = &b;
cout << "c=" << c << "\t&c=" << &c << endl;
cout << "*b=" << *b << "\tb=" << b << "\t&b=" << &b << endl;
cout << "**a=" << **a << "\t*a=" << *a << "\ta=" << a << endl;
return 0;
}
输出如下:
c=10 &c=00B3F7C0
*b=10 b=00B3F7C0 &b=00B3F7B4
**a=10 *a=00B3F7C0 a=00B3F7B4
可以看到b
指向了c
的地址,而a
指向了b
的地址,所以对a
解引用一次得到的是b
的值,即c
的地址,再解引用一次就是c
的值.
7 指针与函数的使用
指针既可以作为返回值类型也可以作为函数的参数列表
如下:
int* changeP(int* M_p) {
int M_a = 100;
M_p = &M_a;
return M_p;
}
int main()
{
int a = 10;
int* p = &a;
cout << "*p=" << *p << endl;
cout << "a=" << a << endl;
p = changeP(p);
a = *changeP(&a);
cout << "*p=" << *p << endl;
cout << "a=" << a << endl;
return 0;
}
输出如下:
*p=10
a=10
*p=100
a=100
注意:本例子中changeP()
返回的是一个int*
类型的值,故在赋值于a
的时候,应该解引用;