技术文章翻译(九) -- 理解C++指针

本人声明

1.本栏仅为归档自己看到的优秀文章;
2.文章版权归原作者所有;
3.因为个人水平有限,翻译难免有错误,请多多包涵。

原文地址

https://www.codeguru.com/cpp/cpp/algorithms/understanding-c-pointers.html

文章正文

理解C++指针

作者:Manoj Debnath 发表于:2017.02.17
指针可以说是C/C++编程中的最强大的功能。指针也被认为是一种可怕的东西,然而我们并没有必要这么想。现代编程语言已经将这个想法置之不理,认为它弊大于利。从高峰时间的生产力角度来看,这种担忧似乎是合乎逻辑的。使用指针的危险在于必须对症下药,并要对其的误用负全部的责任。去除指针毫无疑问可以减少编程中的一个风险,但却牺牲了我们本可以拥有的调整内存的一个手段。这就是C / C++在编写低级程序时无法被替代的原因。但是,在C ++中,我们也可以完全不使用指针(显示使用)来获得相同的结果,那么,使用指针的乐趣在哪里呢?使用指针的理念,可以真正加深我们对工作复杂性的了解。本文试图为这个概念提供一个指针(双关语),并将其进一步引申到更底层(sic)。

  • 变量
    在C /C++编程中,变量是我们可以通过程序操作的内存位置的自定义名称。它被定义为特定的类型,例如int,char,float,double,short,long,Employee,Item等。定义的类型包含了一些信息:首先是使用的内存大小,其次是可以存储的值的范围。例如,当我们声明一个简单的字符变量时,我们得到其支持的值范围的不同的变体,而不必改变使用内存的大小。
char ch;            // size=1 byte/8 bit, range=-128 to127,
                    // equivalent to signed char ch;
unsigned char uch;  // size=1 byte/8 bit, range=0 to 255

对于C / C ++中的其他数据类型,也符合类似的推论。

  • C++指针和引用

指针是其存储值的内存地址,而不是变量自身的值。这是C++中使用的一种复合类型。另一种叫做引用。引用基本上是指相同内存地址变量的别名或替代名称。另一方面,指针用于变量的间接访问。指针与引用对象的主要区别:指针是一个单独的实体,可以单独处理,如赋值,复制和在其生命周期内改变其指向的位置。另一方面,引用在这个问题上则非常严格。我们甚至无法声明未进行分配操作的引用。

int val=10;           // integer variable with a initialized value
int &refval1;         // uninitialized, Not OK
int &refval2=val;     // initialized, OK
int *pval1;           // uninitialized, OK
int *pval2=0;         // initialized, OK, equivalent to nullptr
int *pval3=nullptr;   // initialized, OK
int *pval4=&val;      // initialized OK, pointing to the
                      // location designated by val

注意:在C++中,nullptr表示符是一种指针字符串。其表示该指针是空的,这种含义是不言自明的。指针内容都有包含一种类型,如真假是bool类型的内容;整数(比如12),是int类型的内容,其他的则依此类推。

  • C++指针变量的处理
    很明显,指针用于指代存储器中的地址,因为它保存的值是存储器位置的地址。另外,指针变量必须与它指向的变量的类型一致。这意味着,整形指针可以保存整形变量的内存地址,浮点型指针可以保存浮点型变量的地址。

为了更好的理解下面的语句,请记住,*表示解除引用,而&符号表示取地址。因此,当我们进行以下声明时:

int ival=12;   // integer variable initialized with value 12
int *pi=&i;    // integer pointer holding the address of ival,
               // & means the address

在内存中,它的表示形式如下。内存地址以十六进制数来进行表示的。每次运行代码时,指针指向的实际地址可能会有所不同。
这里写图片描述
图1: 内存地址表示
现在,请观察下面的代码,并注意代码中给出的相应注释。

#include<iostream>
using namespace std;

int main()
{
   int ival=12;

   int *pi=&ival;

   cout<<ival<<endl;       // prints the value of ival, 12

   cout<<*pi<<endl;        // prints the value of ival, 12
                           // through pointer dereference

   cout<<&ival<<endl;      // address of ival

   cout<<pi<<endl;         // address of ival or the contents
                           // of pointer pi

   cout<<&pi<<endl;        // address of pi

   cout<<*(&ival)<<endl;   // address of ival dereferenced,
                           // i.e. the value of ival, 12
   return 0;
}

我们也可以创建一个指针,用来指向另一个指针。这称为双级指针。按照类似的方式,也可以存在三级指针,以此类推。将以下代码片段添加到前面的程序中,并结合给出的注释来计算程序输出结果。

这里写图片描述
图2:双级指针

int main(){
   // ...

   int **ppi=&pi;

   // ...
   cout<<**ppi<<endl;   // contents of ival
   cout<<&*ppi<<endl;   // address pi
   cout<<*ppi<<endl;    // contents of ppi=address of pi
   cout<<&ppi<<endl;    // address of ppi
   // ...
}

指针还涉及其他一些概念,我们会在另一篇文章中讲解它们。但是,这里讲述的是处理指针变量的基本思想。现在,让我们转至指针的另外一种有趣的功能,它被称为函数指针。

  • 函数指针
    将指针的概念向前推进一步,我们可以得到一种特殊的指针,其被称为指向函数的指针或函数指针,该指针指向的是一个函数,而不是一个对象。它的具体用途是提供编程的灵活性。与其他类型的指针相类似,它的类型是由函数的返回值和函数声明的参数类型来确定的。例如,函数声明为以下形式:
int lenconst string&);

该函数的返回值类型为int,参数为const string&。现在,如果我们想要一个指向该函数的指针,我们按照以下形式来进行声明:

int (*pf) (const string &);   // pointer to function
                              // that returns an integer value
int* func(const string &);    // a function that returns
                              // an integer pointer

括号(* pf)是必不可少的;否则,它将表达其他的含义,正如上面所展示的那样。现在,为了初始化它,我们可以按照以下的方式进行。以下两者都是正确的初始化方法。

pf=len;
pf=&len ;

因此,我们按照以下的任意一种方式来调用该函数:

int i1=pf("hello");
int i2=(*pf)("hello");
int i3=len("hello");

下面是讲述该关于该概念的完整代码列表:

#include<iostream>
using namespace std;

int len(const string &str){
   return str.length();
}

int main()
{
   int (*pf)(const string &);
   pf=&len;
   cout<<(*pf)("hello")<<endl;
   cout<<len("hello")<<endl;
   cout<<pf("hello")<<endl;
   return 0;
}
  • 重载函数指针
    乍一看,指针重载的函数可能会让人觉得比较混乱,但编译器会非常智能地处理它们。
#include<iostream>
using namespace std;

int add(int a, int b){
   return a+b;
}

float add(float a, float b){
   return a+b;
}

int main()
{
   int (*pf1)(int,int);
   float (*pf2)(float,float);
   pf1=add;
   pf2=add;
   cout<<pf1(10,20)<<endl;
   cout<<pf2(3.4,1.2)<<endl;

   return 0;
}

编译器会根据上下文,来确定函数指针的类型,该函数指针必须指向确切的函数。

  • 将函数指针作为参数使用
    有趣的是,函数指针可以用作另一个函数的参数。例如,我们可以向前面的代码添加一个函数,该函数将函数指针作为其参数。
float product(float f1,float(*pf)(float,float)){
   return f1 * pf(2.5,2.5);
}

// ...

cout<<product(2.6,add)<<endl;
  • 结论
    严格来说,指针的概念可以追溯至汇编语言时代; C语言适用于通用编程,C ++甚至可以用于在今代进行编程。 C / C ++中指针的强大功能,使得它们可用于任何类型的软件开发,从高级应用程序到低级驱动程序。指针涉及极大的复杂性。本文只是对其使用做了简单的介绍,仅是其冰山一角。需要更多的文章,才能对其进行更全面地介绍。然而,这里给出的简洁理念,可以作为一个较好的开端。

  • 关于作者
    Manoj Debnath
    manosolireap@outlook.com

  • 相关文章
    Last.fm Open Sources C++ Moost Library Now Available
    CppDepend Pro License for C\C++ open source project contributors

栏目导航
上一篇:技术文章翻译(八) – 理解C++文件处理
下一篇:技术文章翻译(十) – 跨平台测试的五大问题

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值