1.概述
程序在运行时,所有的程序和数据都存放在内存中。内存是以字节为单位的连续的存储空间,每个内存单元有唯一的内存编号,称为内存地址,每个变量都有自己的内存地址,系统对内存单元进行操作时,需要通过内存地址找到相应的内存单元。
一般来说,对于简单数据类型,通过变量名就可以对内存单元进行操作,如:i=400,不需要知道内存地址,很多高级程序语言都不允许通过内存地址访问内存单元,C和c++中则可以通过指针类型对允许通过地址来访问内存单元,只不过这个地址的确切值不需要程序员了解;
对于变量i,如果用另一个变量p记住他的内存地址,变量p的数据类型就是指针类型。如果p的内容是I的地址,则称为p指向i;
int i = 20;
int* p = &i;
如果有定义int*p,则说明p是一个指针变量,p的内容应该是一个内存地址,通过取地址符&可以使得p指向另一个存储单元,p本身要占用一个内存单元;
1.指针的声明和初始化
由于指针是变量,需要在使用之前先定义,定义格式:
指针所指的数据类型 *指针变量名1,2.....
如:
int * p_int;
*
称为解除引用运算符,*
两侧的空格可选,一般来说,C语言倾向于使用这种方式:
int *p;//强调*p是一个int 类型的值
c++倾向使用这种方式:
int* p;//强调p是一个指向int类型数据的指针
指针运算符&和*:&是取地址运算符,*是解除引用运算符,二者都是单目运算符。
2.指针注意事项
- 1.在c++中创建指针时,计算机分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存,如:
int *age;
*age = 21;//bad
由于没有说明age指向哪里,那么21将被随机地放在内存中的一个地址中,从而可能会导致错误的发生。
因此,一定要在对指针使用*
运算符之前,将指针初始化为一个确定的地址。
- 2.在定义指针变量时,可以将其定义为指向任何一种基本数据类型的存储单元,当使用取地址运算符为其赋值时,所指向的数据类型要一致,如:
int i;
float *p;
p=&i;
这样是错误的.
- 3.
*
的含义不同,它可以作为算术运算符,还可以作为定义指针使用,还可以作为指针取内容运算符;比如如下示例中:
int i;
int *p= &i;
*p=i*3;
-
4.指针只是概念上的地址,不必关心它的值是什么,因为对于某个变量来说,内存空间是由系统分配的,在C语言中不能将内存地址赋值给一个指针变量,如:
int *p;p=3000;
这是错误的; -
5.指向相同的数据类型的指针可以相互赋值,如:
int x = 2;
int *pi;
int *pj;
pi=&x;
pj=pi;
这样一来,pj也就指向x了。
3.指针和数组
我们知道,可以通过new来动态申请一个数组,并返回第一个元素的地址:
int* arr = new int[size];
如此一来,指针arr就指向arr[0].
我们又知道,数组是一段连续的存储空间,数组名是该存储空间的首地址:
int arr2[5];
arr2[0]是数组arr2中的第一个元素,所以数组名arr2表示arr2[0]的首地址。
因此在C++中,大多数情况下,指针和数组基本等价,两者可以相互替代。
在如下示例中,可以通过指针来操作数组:
#include <iostream>
using namespace std;
const int Size = 5;
int main()
{
int arr[Size] = {1,2,3,4,5};
for(int i =0;i< Size;i++)
{
cout << "arr[" << i<< "]:" << *(arr+i) << endl;
}
return 0;
}
然而也有例外,在以下三种情况下,数组名和指针不同:
- 1.数组声明使用数组名来标记存储位置;
- 2.数组名使用
sizeof()
运算符时,返回整个数组的长度; - 3.将地址运算符&用于数组名时,将返回整个数组的地址;
一般情况下,以下语句都是允许的:
int arr[3] = {1,2,3};
int * p = arr;
cout << arr[0] << endl;//数组索引访问元素
cout << p[0] << endl;
cout << *(p+1) << endl;//指针访问元素
cout << *(arr+2) << endl;
4.指针算数
将整数变量加1,其值将加1,但将指针变量加1后,增加的量为它指向的类型的字节数。
#include <iostream>
int main()
{
using namespace std;
double wages[3] = {10000.0,20000.0,30000.0};
short stacks[3] = { 3,2,1 };
double * pw = wages;
short * ps = &stacks[0];
cout << "pw = " << pw << ", *pw = " << *pw << endl;
pw += 1;
cout << "add one to the pw pointer:" << endl;
cout << "pw = " << pw << ", *pw = " << *pw << endl;
cout << "ps = " << ps << ", *ps = " << *ps << endl;
ps += 1;
cout << "add one to the ps pointer:" << endl;
cout << "ps = " << ps << ", *ps = " << *ps << endl;
cout << "access two element with array notation:" << endl;
cout << "stacks[0] =" << stacks[0] << ",stacks[1] = " << stacks[1] << endl;
cout << "access two element with pointer notation:" << endl;
cout << "*stacks =" << *stacks<< ",*(stacks+1) = " << *(stacks + 1) << endl;
cout << "sizeof(wages):" << sizeof wages << endl;
cout << "sizeof(pw)" << sizeof pw << endl;
system("pause");
return 1;
}
从以上示例中可得到如下结论:
- 1.
array[i] => *(array+1)
,arrsy表示数组 - 2.
*(pointer+1) => pointer[i]
,pointer表示指针; - 3.c++允许将指针和一个整数相加,加1的结果为原来的地址加上指向对象的类型占用的字节数。
因此,很多情况下,可以以相同的方式使用指针和数组名。
5.指针和字符串
由于数组和指针的特殊关系,因此这种关系同样使用于C风格的字符串:
char name[20] = {'h','e','l','l','o',' ','w','o','r','l','d','\0'};
char name[20] = "hello world";
const char * p_name = "hello world";//将字符串常量地址赋给了p_name.
在c++中,用引号括起的字符串也像数组一样,是第一个元素的地址。因此,对于数组中的字符串、用引号括起的字符串、指针所描述的字符串,处理方式都一样,传递他们的地址。
注意上面最后一种指针类型的字符串,使用了const来修饰,因为字符串字面值是常量,c++中禁止将字符串常亮赋给char *指针,如果是char *p_name = "hello world";
,则会编译出现如下错误:
ptrstr2.cpp: In function ‘int main()’:
ptrstr2.cpp:7:18: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
一般来说,给cout提供一个指针,它将打印地址,但如果是char * 类型的指针,则将打印指向的字符串:
#include <iostream>
#include <cstring>
int main()
{
using namespace std;
const char * animal = "pigge";
int * pint = new int;
cout << "animal: " << animal << endl;
cout << "(int *)animal: " <<(int *) animal << endl;
cout << "pint: " << pint << endl;
return 1;
}
/**
animal: pigge
(int *)animal: 0x400a65
pint: 0x195ac20
/
初始化char数组时使用=,但将字符串赋值给char数组时,应使用
cstring
中提供的strcpy()
和strncpy()
函数.(这也侧面反映出c++的string类较C风格更简单)。
6.指针访问结构中的成员
可以使用new创建一个未命名的结构类型变量,其创建格式和创建动态数组一样。但访问结构成员,需要一种新的运算符->
来进行,如下示例:
//定义一个结构体
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
//创建一个静态结构
inflatable inf = {"test",20.8,23.4};
//创建一个动态结构
inflatable * pstruct = new inflatable;
return 1;
}
访问结构成员
当创建好一个动态结构时,将把该结构的一块内存地址赋给指针。当访问结构内成员时,k可以有如下两种方式:
- 1.不能使用成员运算符(因为不知道结构名称,只知道地址),应使用箭头成员运算符
->
:
cout << pstruct->name << endl;
- 2.由于pstruct是指针,则
*pstruct
就是指针指向的值,即结构本身,因此如下形式也可以:
cout << (*pstruct).name << endl;
下面是一个示例:
#include <iostream>
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
using namespace std;
inflatable * ps = new inflatable;
cout << "Enter name of inflatable:";
cin.get(ps->name,20).get();
cout << "Enter volume in cubic feet: ";
cin >> ps->volume;
cout << "Enter price: " << endl;
cin >> ps->price;
cout << "name: " << (*ps).name << endl;
cout << "Volume: " << ps->volume << endl;
cout << "Price: $" << ps->price << endl;
delete ps;//释放内存,必须和new配对使用
return 1;
}
/**
编译并运行时
$ ./newstruct
Enter name of inflatable:booker
Enter volume in cubic feet: 12
Enter price:
34.3
name: booker
Volume: 12
Price: $34.3
$
/
7.二级指针
指向指针的指针称为二级指针,如:
int i = 3;
int *p = &i;
int **pp = &p;
因此可以得到:
*pp = p;
**pp = *p = i;
关于二级指针更多的内容会在学习二维数组时说明。如下是一个指针和二级指针的示例:
#include <iostream>
int main()
{
using namespace std;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int * p = arr;
cout << "arr at " << arr << " address." << endl;
cout << "p at " << p << " address." << endl;
cout << p[3] << endl;
cout << "arr[1]:" << arr[1] << ", *(p+1):" << *(p+1) << endl;
int ** pp = &p;
cout << "pp at " << pp << " address." << endl;
cout << "*pp address:" << *pp << ",p address:" << p << endl;
cout << "**pp value:" << **pp << ",*p value:" << *p << endl;
cout << "*(*pp+1) value:" << *(*pp+1) << ",p+1 value:" << *(p+1) << endl;
return 1;
}
/**
arr at 0x7ffcf3258310 address.
p at 0x7ffcf3258310 address.
4
arr[1]:2, *(p+1):2
pp at 0x7ffcf3258300 address.
*pp address:0x7ffcf3258310,p address:0x7ffcf3258310
**pp value:1,*p value:1
*(*pp+1) value:2,p+1 value:2
/
下图表示指针和二级指针的加法图示:
以下示例中包括了new创建动态数组、动态结构以及和指针之间的使用:
#include <iostream>
struct antaractica_years_end
{
int year;
};
int main()
{
antaractica_years_end s01,s02,s03;
s01.year = 1998;
antaractica_years_end * pa = &s02;
pa->year = 1999;
antaractica_years_end trio[3];
trio[0].year = 2003;
std::cout << "trio->year:" <<trio->year << std::endl;
antaractica_years_end * arp[3] = {&s01,&s02,&s03};
std::cout << "arp[1]->year:" << arp[1]->year << std::endl;
antaractica_years_end ** ppa = arp;
auto ppb = ppa;
std::cout << "(*ppa)-year:" << (*ppa)->year << std::endl;
std::cout << "(*(ppb+1))->year:" << (*(ppb+1))->year << std::endl;
return 1;
}
/**
trio->year:2003
arp[1]->year:1999
(*ppa)-year:1998
(*(ppb+1))->year:1999
/