C++指针

来源:互联网 发布:广告牌制作软件手机 编辑:程序博客网 时间:2024/05/22 12:08

简介

定义

指针是“指向”另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问,然而指针与引用相比又有很多不同点:

  • 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。
  • 指针无需再定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没被初始化,也将拥有一个不确定的值。

基本使用

获取对象的地址
指针存放某个对象的地址,要想获取该地址,需要使用取地址符(&):

Int a=1;Int *b=&a;

指针值
指针的值应属于下列4种状态之一:
- 指向一个对象。
- 指向紧邻对象所占空间的下一个位置。
- 空指针,意味着指针没有指向任何对象。
- 无效指针,也就是上述情况之外的其他值。
利用指针访问对象
如果指针指向了一个对象,则允许使用解引用符(*)来访问该对象。
空指针
以下列出几个生成空指针的方法:

Int *p1=nullptr;Int p2=0;Int *p3=NULL;

nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。
NULL在头文件cstdlib中定义,它的值是0。

其他指针操作

bool
如果指针的值是0,条件取false
void*指针
void*是一种特殊的指针类型,可用于存放任意对象的地址。一个void*指针存放一个地址,这一点和其他指针类似。不同的是,我们队该地址中到底是个什么类型的对象并不了解。
利用void*指针能做的事比较有限:拿他和别的指针比较、作为函数的输入或输出,或者赋给另外一个void*指针。不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
概括说来,以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。

基本应用

复合类型的声明

在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。也就是说,一条定义语句可能定义出不同类型的变量。
指向指针的指针
通过*的个数可以区分指针的级别。
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。

const

const指针有两种:
- 指向常量的指针:即指针指向的对象是常量。不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针。
- 常量指针:即指针本身是常量,它所指向的地址无法修改。指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定位常量。常量指针必须初始化。

放在const关键字之前用以说明指针是一个常量,即指针所指向的地址不能变;放在const之后类型之前说明指针所指向的对象是一个常量。

顶层const和底层const

关于顶层const和底层const,这一篇分析的不错:
https://segmentfault.com/q/1010000002568358/a-1020000002573278
顶层const(*在const前):表示指针本身是个常量
底层const(*在const后):表示指针所指的对象是一个常量
指针可以是自身(指针本身),可以是别人(指向别人)。所以指针既可以是顶层const,又可以是底层const。
对于指针:

  • 底层const可以修改指针的指向,也就是更换指针所指向的对象,但无法修改指针所指向的对象的内容。
  • 顶层const可以修改指针所指向对象的内容,但是无法修改指针的指向。

constexpr

一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
指针和constexpr
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效。constexpr把它所定义的对象设置为了顶层const。

数组

在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。这导致在一些情况下数组的操作实际上是指针的操作,这一结论有很多隐含的意思。其中一层意思是当使用数组作为一个auto变量的初始值时,得到的类型是指针而非数组:

inti a[]={0,1,2,3,4,5,6,7,8,9};auto ia2(ia);                       //ia2是一个整形指针,指向ia的第一个元素

编译器实际执行的初始化过程类似于下面的形式:

auto ia2(&ia[0]);

当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组:

decltype(ia) ia3={0,1,2,3,4,5,6,7,8,9};

指针也是迭代器

vector和string的迭代器支持的运算,数组的指针全都支持。
通过数组名字或者数组中首元素的地址都能得到指向首元素的指针;不过获取尾后指针就要用到数组的另外一个特殊性质了。我们可以设法获取数组俄日元素之后的那个并不存在的元素的地址:

int *e=&arr[10];

利用上面得到的指针能重写之前的循环,令其输出arr的全部元素:

for(int *b=arr;b!=e;++b)    cout<<*b<<endl;

指针运算

指向树组元素的指针可以执行所有迭代器运算。这些运算,包括解引用、递增、比较、与整数相加、两个指针相减等,用在指针和用在迭代器上意义完全一致。
两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的及其相关的类型。因为差值可能为负值,所以ptrdiff_t是一种带符号类型。

下标和运算

在很多情况下使用数组的名字其实用的是一个指向数组首元素的指针。一个典型的例子是当对数组使用下标运算符时,编译器会自动转换为指针来执行。
只要是指针指向的是数组中的元素,都可以执行下标运算:

int *p=&ia[2];int j=p[1];int k=p[-2];

标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,上面的最后一个例子很好地说明了这一点。内置的下标运算符可以处理负值,当然,结果地址必须指向原来的指针所指同一数组中的元素。

表达式

  • 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
  • 因为取地址运算符生成右值,所以decltype(&p)的结果是int**,也就是说,结果是一个指向整形指针的指针。 成员访问运算符

点运算符和箭头运算符都可用于访问成员,其中,点运算符获取类对象的一个成员,箭头运算符与点运算符有关,表达式ptr->men等价于(*ptr).mem。
因为解引用运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号。如果没加括号,代码的含义就大不相同了。
箭头运算符作用于一个指针类型的运算对象,结果是一个左值。点运算符分成两种情况:如果成员所属的对象是左值,那么结果是左值,反之,如果成员所属的对象是右值,那么结果是右值。

sizeof运算符

因为sizeof不会实际求运算对象的值,所以即使p是一个无效的指针也不会有什么影响。在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正的使用。sizeof不需要真的解引用指针也能知道它所指对象的类型。
注意:

  • 对指针执行sizeof运算得到指针本身所占空间的大小。
  • 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效。
  • sizeof运算不会把数组转换成指针来处理。

其他隐式类型转换

数组转换成指针:当数组被用作decltype关键字的参数,或者作为取地址符(&),sizeof及typeid等运算符的运算对象时,转换不会发生。如果用一个引用来初始化数组,转换也不会发生。
指针的转换: C++还规定了几种其他 的指针转换方式,包括常量整数值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void*;指向任意对象的指针能转换成const void*。
转换成布尔类型:如果指针或算数类型的值为0,转换结果是false,否则结果是true
转换成常量:如果T是一种类型,我们就能将指向T的指针或引用分别转换成指向const T的指针或引用。相反的转换并不存在,因为它试图删除掉底层const。

函数中的指针

有返回值的函数

不要返回局部对象的引用或指针
函数完成后,它所占用的存储空间也随之被释放掉。因此,函数终止意味着局部变量的引用将指向不再有效的内存区域。
返回数组指针
虽然从语法上来说,要想定义一个返回数组的指针或引用的函数比较繁琐,但是有一些方法可以简化这一任务,其中最直接的方法是使用类型别名

typedef int arrT[10];using arrT=int[10];arrT* func(int i);      //func返回一个指向含有10个整数的数组的指针

声明一个返回数组指针的函数
如果我们想定义一个返回数组指针的函数,则数组的维度必须跟在函数名字之后。然而,函数的形参列表也跟在函数名字后面且形参列表应该先于数组的维度:

Type (*function(parameter_list))[dimension]int(*fun(int i))[10]{    int a[10]{1};    return &a;}

函数重载

如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的。

函数指针

函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。
使用函数指针
和数组类似,虽然不能定义函数类型的形参,但是形参可以使指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用:

void useBigger(const string &s1,const string &s2,bool pf(const string &,const string &));void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &)); 

上述两个函数声明等价。
返回指向函数的指针
虽然不能返回一个函数,但是能返回指向函数类型的指针。然而,我们必须把返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针类型处理。与往常一样,要想声明一个返回函数指针的函数,最简单的办法是使用类型别名:

using F=int(int*,int);      //F是韩式类型,不是指针using PF=int(*)(int*,int);      //PF是指针类型

使用:

PF f1(int);     正确:PF是指向函数的指针,f1返回指向函数丶指针F f1(int);          错误:F是函数类型,f1不能返回一个函数F *f1(int);         正确:显式地指定返回类型是指向函数的指针

也能用下面的形式直接声明f1:

int (*f1(int))(int*,int);

还可以使用尾置返回类型的方式声明一个返回函数指针的函数:

auto f1(int) ->int (*)(int*,int);

将decltype用于函数指针类型
牢记我们将decltype作用于某个函数时,它返回函数类型而非指针类型。因此,我们显示地加上*以表明我们需要返回指针,而非函数本身。

修改隐式this指针的类型

我们可以在成员函数体内部使用this,因此尽管没有必要,但我们还是能把A定义成如下的形式:

std::string A () const{return this->B;}

引入const成员函数
const的可用来修改隐式this指针的类型。
默认情况下,this的类型是指向类类型非常量版本的常量指针。尽管this是隐式的,但它仍然需要遵循初始化规则,意味着(默认情况下)我们不能把this绑定到一个常量对象上。这一情况也就使得我们不能在一个常量对象上调用普通的成员函数。
如果A是一个普通函数而且this是一个普通的指针参数,则我们应该吧this声明成const ClassName *const。毕竟,在A的函数体内不会改变this所指的对象,所以把this设置为指向常量的指针有助于提高函数的灵活性。
然而,this是隐式的并且不会出现在参数列表中,所以在哪儿将this声明成指向常量的指针就称为我们必须面对的问题。c++语言的做法是允许把const关键字放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。

转载请注明出处:http://blog.csdn.net/ylbs110/article/details/50933537

1 0
原创粉丝点击