本文主要是谭浩强老师c++教材第三版第六章的小结。
1.变量与指针
(1)指针的定义
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。在Visual C++中为每个指针变量分配4个字节的存储空间。
在定义指针变量时需注意:
a. 在定义指针变量时必须指定基类型,只有知道数据类型,才能按该类型的长度从正确长度的地址中读取该数据;
b. 不能用一个整数给指针变量赋值,系统将不会认为这是地址,而认为这是整数,从而报错,如:
int *point_1 = 2000 //非法
int i;
int *point_1 = &i //合法
(2)引用指针变量
两个运算符:
&:表示取地址运算符
*:表示指针运算符(或间接访问运算符)
取地址和赋值运算例子:
int *point_a,a;
a=100;
point_a = &a //取地址表达式
*point_a = a //赋值表达式
对于&和*运算符的说明:
这两个运算符优先级相同,但是按照从右向左的方向结合:
如:&*point_a的含义是先用*取出a的值,再用&取a的地址,因此它等价于&a;
同理:*&a的含义是先用&a取出a的地址,再用*取值,因此它等价于变量a。
关于指针变量*point_1符号在定义和引用时的区别:
在定义时,*point_1代表了一个指针变量,因此不能用数值给它赋初值,它会将数值当做地址从而报错,而应该将取地址符&a赋给它作初值;
在引用时,*point_1代表了所指向元素a的值,因此地址&a应该赋给point_1,而不是*point_1,详情见如下例子:
#include <iostream>
using namespace std;
int main()
{
int a,b;
a = 100,b = 10;
int *point_1 = &a;
int *point_2;
point_2 = &b;
cout << *point_1 << "\t";
cout << *point_2;
}
输出为100,10
(3)用指针作函数参数
指针变量用作函数时,实参和形参变量始终是“值传递”的过程,因此形参中若传入的是地址则只改变地址,而不改变原地址的指向,若传入的是值则只改变值,而不改变实参的指向。具体分析看下边三个例子:
题目:找出两数中的大数和小数
主函数:
#include <iostream>
using namespace std;
int main()
{
void swap(int *p1, int *p2);
int *point_1, *point_2,a,b;
cin >> a >>b;
point_a = &a;
point_b = &b;
if(a<b) swap(point_a,point_b);
cout << "max = " << a <<" min = " << b <<endl;
return 0;
}
主函数要注意的地方是:
引用函数语句:if(a<b) swap(point_a,point_b)这里放的是指针变量,不能写成*point_a,因为*在引用里表示取值,会造成变量类型不符。
swap函数正确示例如下:
void swap(int *p1, int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
指针变量做实参传给形参,在形参中交换了指针变量所指向的值,因此可以实现a,b值的改变
输入a=45,b =78
运行结果为:max=78, min =45
变量改变过程如下:
此处要注意一个貌似对实际不对的写法:
void swap(int *p1, int *p2)
{
int *temp;
*temp = *p1;
*p1 = *p2;
*p2 = *temp;
}
此处将temp作为一个指针变量,它的错误点在于:未对temp赋值,因此它是不可预见的,这种情况下对*temp赋值是有危险的,会破坏系统工作状况,也就是说不能定义一个指针变量后,对它所指向的值直接赋值,而应该对它先赋地址值,因此要用一个整型变量temp来接收值。
由于交换地址值所造成的错误写法:
void swap(int *p1, int *p2)
{
int *temp;
temp = p1;
p1 = p2;
p2 = temp;
}
当输入a = 45,b=78时,输出的结果是:
max= 45,min =78
出现该错误的原因是:只改变了实参指针变量的值,但并没有改变指针的指向,也就是单纯换了地址,并没有换值,分析过程如下:
最后是一种不用指针单纯用整型变量的错误写法:
void swap(int x, int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
这是一般的参数传递,是单向的值传递过程,只能从实参传给形参,形参的改变无法传回给实参,具体变化如下:
一般变量与指针变量做实参的区别在于:
指针变量做实参时,传给形参的是地址,相当于二者共用了一段地址,因此改变形参地址中的值一定会改变实参的地址中的值
而一般变量做实参时,形参只是与实参有相同值的不同变量,因此形参改变不影响实参的改变。
总而言之,要改变实参的值,一定要使指针变量指向值发生改变。
2. 数组与指针
即指向数组元素的指针。
int a[10];
int *p;
以下两个语句等价:
p = & a[0];
p = a;
数组名代表数组首元素的指针,p+1则代表下一个元素的指针
因此引用数组元素时,可以使用下标法,如a[i],也可使用指针法,如*(a+i)或者*(p+i)
指针也可以有自增和自减算法,如p++,这种方法由于不用每次重新计算地址,是最快的方式。
使用指针数组时,a[10]不是合法元素,但是由于是找地址,也就是p+10系统不认为它非法,因此要注意避免这种情况。
至于做参数的情况,与上述指针变量的理解一致。
3. 字符串与指针
字符串的初始化方法:
(1)用字符数组存放一个字符串:
如:char str[] = "I love China!"
(2)用字符串变量存放一个字符串:
如:string str = "I love China!"
(3)用字符指针指向一个字符串
如:char *str = "I love China!"
或者:
char *str;
str = "I love China!"
4. 函数与指针
指针变量也可以指向一个函数,一个函数在编译时被分配给一个入口地址,这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
定义形式:
函数类型 (* 变量名)(函数的形参列表)
例如:
求a和b中的大者
#include <iostream>
using namespace std;
int main()
{
int max(int x, int y);
int (*p)(int, int);
int a,b,m;
p =max;
cin>>a>>b;
m = p(a,b);
cout <<"max= "<<m<<endl;
return 0;
}
int max(int x, int y)
{
int z;
if(x>y)z=x;
else z=y;
return(z);
}
5.返回指针值的函数
C++ 允许从函数返回指针,可以理解为指针型函数。为了做到这点,必须声明一个返回指针的函数,如下所示:
类型名 *函数名(参数列表)
如:int *(int x, int y)
6. 指针数组
如果一个数组的元素均为指针类型数据,则称该数组为指针数组。
定义为:
类型名 *数组名[数组长度]
如:int *p[4]
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr[MAX];
for (int i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; // 赋值为整数的地址
}
for (int i = 0; i < MAX; i++)
{
cout << "Value of var[" << i << "] = ";
cout << *ptr[i] << endl;
}
return 0;
}
在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。
7.指向指针的指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **var;
实例:
#include <iostream>
using namespace std;
int main ()
{
int var;
int *ptr;
int **pptr;
var = 3000;
// 获取 var 的地址
ptr = &var;
// 使用运算符 & 获取 ptr 的地址
pptr = &ptr;
// 使用 pptr 获取值
cout << "var 值为 :" << var << endl;
cout << "*ptr 值为:" << *ptr << endl;
cout << "**pptr 值为:" << **pptr << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
var 值为 :3000
*ptr 值为:3000
**pptr 值为:3000
更为经典的例子:
#include <iostream>
using namespace std;
int main()
{
char **p;
char *name[] = {"BASIC","FORTRAN","C++"};
p = name +2;
cout << *p;
cout << **p;
}
运行结果:
C++
C
解释:由于p = name+2 = name[2],因此p中存放的是name[2]首字符的地址,因此取值cout <<*p所得的是从name[2]中依次输出字符,而**p输出的是*p的值,也就是C++的第一个字符。
8. 空指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。空指针是“指向空类型”或者“指向不确定类型”。
NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include <iostream>
using namespace std;
int main ()
{
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
ptr 的值是 0
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
9.引用
对于引用可以理解为为变量取了一个别名,它是c++的重要扩充。
引用使用引用声明符来实现的,具体例子如下:
int a;
int &b = a;//声明b是a的引用,也就是说给a取了个别名叫做b
可以理解为:通过b可以引用a;
此处&是引用声明符而不是地址运算符,此处的区别主要是:出现在声明中的&是引用声明符,其他情况是地址运算符。
如:
int &b =a; //声明b是a的引用;
cout << &b; // 输出b的地址。
对于引用需要注意的几点:
a. 引用不是一种独立的数据类型,它只是个别名,因此对于引用只有声明,没有定义。
b. 声明一个引用时,必须同时对其初始化,即声明它为哪一个变量的引用;
c. 在声明一个引用后,不能作为另一个变量的引用;
d. 不能建立引用数组,既不能引用数组元素,也不能引用数组名,也不能建立指针的引用;
e. 可以取引用的地址:
如:
int a;
int &b = a;
int *pt;
pt = &b;//取引用b的地址,也就是把a的地址赋给pt。
引用作函数参数时,系统会自动把实参的地址传给形参,因此是址传递。这可以理解为:当引用作形参时,实参传来之后相当于被引用改了个名字,值和地址都是实参本身,实参和形参公用存储空间,因此对于形参的改变就相当于在实参本身上改,因此会影响实参的值。
一般变量做参数、指针变量做参数、引用变量做参数的具体比较请见链接:
https://jingyan.baidu.com/article/8065f87fed913f23312498c3.html
本文主要参考:http://www.runoob.com/cplusplus/cpp-null-pointers.html