C++ 高级数据类型(三)—— 指针

我们已经明白变量其实是可以由标识来存取的内存单元。但这些变量实际上是存储在内存中具体的位置上的。对我们的程序来说,计算机内存只是一串连续的单字节单元(1byte cell),即最小数据单位,每一个单元有一个唯一地址。

计算机内存就好像城市中的街道。在一条街上,所有的房子被顺序编号,每所房子有唯一编号。因此如果我们说芝麻街27号,我们很容易找到它,因为只有一所房子会是这个编号,而且我们知道它会在26号和28号之间。

同房屋按街道地址编号一样,操作系统(operating system)也按照唯一顺序编号来组织内存。因此,当我们说内存中的位置1776,我们知道内存中只有一个位置是这个地址,而且它在地址1775和1777之间。

 

地址操作符/去引操作符 Address/dereference operator (&)

当我们声明一个变量的同时,它必须被存储到内存中一个具体的单元中。通常我们并不会指定变量被存储到哪个具体的单元中—幸亏这通常是由编译器和操作系统自动完成的,但一旦操作系统指定了一个地址,有些时候我们可能会想知道变量被存储在哪里了。

这可以通过在变量标识前面加与符号ampersand sign (&)来实现,它表示"...的地址" ("address of"),因此称为地址操作符(adress operator),又称去引操作符(dereference operator)。例如:

ted = &andy;

将变量andy的地址赋给变量ted,因为当在变量名称andy 前面加ampersand (&) 符号,我们指的将不再是该变量的内容,而是它在内存中的地址。

假设andy 被放在了内存中地址1776的单元中,然后我们有下列代码:

andy = 25;
fred = andy;
ted = &andy;

其结果显示在下面的图片中:

我们将变量andy 的值赋给变量fred,这与以前我们看到很多例子都相同,但对于ted,我们把操作系统存储andy的内存地址赋给它,我们想像该地址为1776 (它可以是任何地址,这里只是一个假设的地址),原因是当给ted 赋值的时候,我们在andy 前面加了ampersand (&) 符号。

存储其它变量地址的变量(如上面例子中的ted ),我们称之为指针(pointer)。在C++ 中,指针pointers 有其特定的优点,因此经常被使用。在后面我们将会看到这种变量如何被声明。

 

引用操作符Reference operator (*)

使用指针的时候,我们可以通过在指针标识的前面加星号asterisk (*)来存储该指针指向的变量所存储的数值,它可以被翻译为“所指向的数值”("value pointed by")。因此,仍用前面例子中的数值,如果我们写: beth = *ted; (我们可以读作:"beth 等与ted所指向的数值") beth 将会获得数值25,因为ted 是1776,而1776 所指向的数值为25。

你必须清楚的区分ted 存储的是1776,但*ted (前面加asterisk * ) 指的是地址1776中存储的数值,即25。注意加或不加星号*的不同(下面代码中注释显示了如何读这两个不同的表达式):

beth = ted; // beth 等于 ted ( 1776 )
beth = *ted; // beth 等于 ted 所指向的数值 ( 25 )

 

地址或反引用操作符Operator of address or dereference (&)

它被用作一个变量前缀,可以被翻译为“…的地址”("address of"),因此:&variable1 可以被读作 variable1的地址("address of variable1" )。

引用操作符Operator of reference (*)

它表示要取的是表达式所表示的地址指向的内容。它可以被翻译为“…指向的数值” ("value pointed by")。

* mypointer 可以被读作 "mypointer指向的数值"。

继续使用上面开始的例子,看下面的代码:

andy = 25;
ted = &andy;

现在你应该可以清楚的看到以下等式全部成立:

andy == 25
&andy == 1776
ted == 1776
*ted == 25

第一个表达式很容易理解,因为我们有赋值语句andy=25;。第二个表达式使用了地址(或反引用)操作符(&) 来返回变量andy的地址,即 1776。第三个表达式很明显成立,因为第二个表达式为真,而我们给ted赋值的语句为ted = &andy;。第四个表达式使用了引用操作符 (*),相当于ted指向的地址中存储的数值,即25。

由此你也可以推断出,只要ted 所指向的地址中存储的数值不变,以下表达式也为真:

*ted == andy

声明指针型变量Declaring variables of type pointer

由于指针可以直接引用它所指向的数值,因此有必要在声明指针的时候指明它所指向的数据类型。指向一个整型int或浮点型float数据的指针与指向一个字符型char数据的指针并不相同。

因此,声明指针的格式如下:

type * pointer_name;

这里,type 是指针所指向的数据的类型,而不是指针自己的类型。例如:

int * number;
char * character;
float * greatnumber;

它们是3个指针的声明,每一个指针指向一种不同数据类型。这三个指针本身其实在内存中占用同样大小的内存空间(指针的大小取决于不同的操作系统),但它们所指向的数据是不同的类型,并占用不同大小的内存空间,一个是整型int,一个是字符型char ,还有一个是浮点型float。

需要强调的一点是,在指针声明时的星号asterisk (*) 仅表示这里声明的是一个指针,不要把它和前面我们用过的引用操作符混淆,虽然那也是写成一个星号 (*)。它们只是用同一符号表示的两个不同任务。

// my first pointer
#include <iostream.h>

int main ( ) {
int value1 = 5, value2 = 15;
int * mypointer;
mypointer = &value1;
*mypointer = 10;
mypointer = &value2;
*mypointer = 20;
cout << "value1==" << value1 << "/ value2==" << value2;
return 0;
}
value1==10 / value2==20

注意变量value1 和 value2 是怎样间接的被改变数值的。首先我们使用 ampersand sign (&) 将value1的地址赋给mypointer 。然后我们将10 赋给 mypointer所指向的数值,它其实指向value1的地址,因此,我们间接的修改了value1的数值。

为了让你了解在同一个程序中一个指针可以被用作不同的数值,我们在这个程序中用value2 和同一个指针重复了上面的过程。

下面是一个更复杂一些的例子:

// more pointers
#include <iostream.h>

int main () {
int value1 = 5, value2 = 15;
int *p1, *p2;
p1 = &value1; // p1 = address of value1
p2 = &value2; // p2 = address of value2
*p1 = 10; // value pointed by p1 = 10
*p2 = *p1; // value pointed by p2 = value pointed by p1
p1 = p2; // p1 = p2 (value of pointer copied)
*p1 = 20; // value pointed by p1 = 20
cout << "value1==" << value1 << "/ value2==" << value2;
return 0;
}
value1==10 / value2==20

上面每一行都有注释说明代码的意思:ampersand (&) 为"address of",asterisk (*) 为 "value pointed by"。注意有些包含p1 和p2 的表达式不带星号。加不加星号的含义十分不同:星号(*)后面跟指针名称表示指针所指向的地方,而指针名称不加星号(*)表示指针本身的数值,即它所指向的地方的地址。

另一个需要注意的地方是这一行:

int *p1, *p2;

声明了上例用到的两个指针,每个带一个星号(*),因为是这一行定义的所有指针都是整型int (而不是 int*)。原因是引用操作符(*) 的优先级顺序与类型声明的相同,因此,由于它们都是向右结合的操作,星号被优先计算。我们在 section 1.3: Operators 中已经讨论过这些。注意在声明每一个指针的时候前面加上星号asterisk (*)。

 

指针和数组Pointers and arrays

数组的概念与指针的概念联系非常解密。其实数组的标识相当于它的第一个元素的地址,就像一个指针相当于它所指向的第一个元素的地址,因此其实它们是同一个东西。例如,假设我们有以下声明:

int numbers [20];
int * p;

下面的赋值为合法的:

p = numbers;

这里指针p 和numbers 是等价的,它们有相同的属性,唯一的不同是我们可以给指针p赋其它的数值,而numbers 总是指向被定义的20个整数组中的第一个。所以,p只是一个普通的指针变量,而与之不同,numbers 是一个指针常量(constant pointer),数组名的确是一个指针常量。因此虽然前面的赋值表达式是合法的,但下面的不是:

numbers = p;

因为numbers 是一个数组(指针常量),常量标识不可以被赋其它数值。

由于变量的特性,以下例子中所有包含指针的表达式都是合法的:

// more pointers
#include <iostream.h>

int main () {
int numbers[5];
int * p;
p = numbers;
*p = 10;
p++; 
*p = 20;
p = &numbers[2]; 
*p = 30;
p = numbers + 3; 
*p = 40;
p = numbers;
*(p+4) = 50;
for (int n=0; n<5; n++)
cout << numbers[n] << ", ";
return 0;
}
10, 20, 30, 40, 50,

在数组一章中我们使用了括号[]来指明我们要引用的数组元素的索引(index)。中括号[]也叫位移(offset)操作符,它相当于在指针中的地址上加上括号中的数字。例如,下面两个表达式互相等价:

a[5] = 0; // a [offset of 5] = 0
*(a+5) = 0; // pointed by (a+5) = 0

不管a 是一个指针还是一个数组名, 这两个表达式都是合法的。

 

指针初始化Pointer initialization

当声明一个指针的时候我们可能需要同时指定它们指向哪个变量,

int number;
int *tommy = &number;

这相当于:

int number;
int *tommy;
tommy = &number;

当给一个指针赋值的时候,我们总是赋给它一个地址值,而不是它所指向数据的值。你必须考虑到在声明一个指针的时候,星号 (*) 只是用来指明它是指针,而从不表示引用操作符reference operator (*)。记住,它们是两种不同操作,虽然它们写成同样的符号。因此,我们要注意不要将以上的代码与下面的代码混淆:

int number;
int *tommy;
*tommy = &number;

这些代码也没有什么实际意义。

在定义数组指针的时候,编译器允许我们在声明变量指针的同时对数组进行初始化,初始化的内容需要是常量,例如:

char * terry = "hello";

在这个例子中,内存中预留了存储"hello" 的空间,并且terry被赋予了向这个内存块的第一个字符(对应’h’)的指针。假设"hello"存储在地址1702,下图显示了上面的定义在内存中状态:

这里需要强调,terry 存储的是数值1702 ,而不是'h' 或 "hello",虽然1702 指向这些字符。

指针terry 指向一个字符串,可以被当作数组一样使用(数组只是一个常量指针)。例如,如果我们的心情变了,而想把terry指向的内容中的字符'o' 变为符号'!' ,我们可以用以下两种方式的任何一种来实现:

terry[4] = '!';
*(terry+4) = '!';

记住写 terry[4] 与*(terry+4)是一样的,虽然第一种表达方式更常用一些。以上两个表达式都会实现以下改变:

指针的数学运算Arithmetic of pointers

对指针进行数学运算与其他整型数据类型进行数学运算稍有不同。首先,对指针只有加法和减法运算,其它运算在指针世界里没有意义。但是指针的加法和减法的具体运算根据它所指向的数据的类型的大小的不同而有所不同。

我们知道不同的数据类型在内存中占用的存储空间是不一样的。例如,对于整型数据,字符char 占用1 的字节(1 byte),短整型short 占用2 个字节,长整型long 占用4个字节。

假设我们有3个指针:

char *mychar;
short *myshort;
long *mylong;

而且我们知道他们分别指向内存地址1000, 2000 和3000。

因此如果我们有以下代码:

mychar++;
myshort++;
mylong++;

就像你可能想到的,mychar的值将会变为1001。而myshort 的值将会变为2002,mylong的值将会变为3004。原因是当我们给指针加1时,我们实际是让该指针指向下一个与它被定义的数据类型的相同的元素。因此,它所指向的数据类型的长度字节数将会被加到指针的数值上。以上过程可以由下图表示:

这一点对指针的加法和减法运算都适用。如果我们写以下代码,它们与上面例子的作用一抹一样: mychar = mychar + 1;

myshort = myshort + 1;
mylong = mylong + 1;

这里需要提醒你的是,递增 (++) 和递减 (--) 操作符比引用操作符reference operator (*)有更高的优先级,因此,以下的表达式有可能引起歧义:

*p++;
*p++ = *q++;

第一个表达式等同于*(p++) ,它所作的是增加p (它所指向的地址,而不是它存储的数值)。

在第二个表达式中,因为两个递增操作(++) 都是在整个表达式被计算之后进行而不是在之前,所以*q 的值首先被赋予*p ,然后q 和p 都增加1。它相当于:

*p = *q;
p++;
q++;

像通常一样,我们建议使用括号()以避免意想不到的结果。

 

指针的指针Pointers to pointers

C++ 允许使用指向指针的指针。要做到这一点,我们只需要在每一层引用之前加星号(*)即可:

char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;

假设随机选择内存地址为7230, 8092 和10502,以上例子可以用下图表示:

(方框内为变量的内容;方框下面为内存地址)

这个例子中新的元素是变量c,关于它我们可以从3个方面来讨论,每一个方面对应了不同的数值:

c 是一个(char **)类型的变量,它的值是8092

*c 是一个(char*)类型的变量,它的值是7230

**c 是一个(char)类型的变量,它的值是'z'

 

空指针void pointers

指针void 是一种特殊类型的指针。void 指针可以指向任意类型的数据,可以是整数,浮点数甚至字符串。唯一个限制是被指向的数值不可以被直接引用(不可以对它们使用引用星号*),因为它的长度是不定的,因此,必须使用类型转换操作或赋值操作来把void 指针指向一个具体的数据类型。

它的应用之一是被用来给函数传递通用参数:

// integer increaser
#include <iostream.h>

void increase (void* data, int type) {
switch (type) {
case sizeof(char) : (*((char*)data))++; break;
case sizeof(short): (*((short*)data))++; break;
case sizeof(long) : (*((long*)data))++; break;
}
}

int main () {
char a = 5;
short b = 9;
long c = 12;
increase (&a,sizeof(a));
increase (&b,sizeof(b));
increase (&c,sizeof(c));
cout << (int) a << ", " << b << ", " << c;
return 0;
}
6, 10, 13

sizeof 是C++的一个操作符,用来返回其参数的长度字节数常量。例如,sizeof(char) 返回 1,因为 char 类型是1字节长数据类型。

 

函数指针Pointers to functions

C++ 允许对指向函数的指针进行操作。它最大的作用是把一个函数作为参数传递给另外一个函数。声明一个函数指针像声明一个函数原型一样,除了函数的名字需要被括在括号内并在前面加星号asterisk (*)。例如:

// pointer to functions
#include <iostream.h>

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

int subtraction (int a, int b) { 
    return (a-b); 
}

int (*minus)(int,int) = subtraction;
int operation (int x, int y, int (*functocall)(int,int)) {
    int g;
    g = (*functocall)(x,y);
    return (g);
}

int main () {
    int m,n;
    m = operation (7, 5, addition);
    n = operation (20, m, minus);
    cout <<n;
    return 0;
}
8

在这个例子里, minus 是一个全局指针,指向一个有两个整型参数的函数,它被赋值指向函数subtraction,所有这些由一行代码实现:

int (* minus)(int,int) = subtraction;

这里似乎解释的不太清楚,有问题问为什么(int int)只有类型,没有参数,就再多说两句。

这里 int (*minus)(int int)实际是在定义一个指针变量,这个指针的名字叫做minus,这个指针的类型是指向一个函数,函数的类型是有两个整型参数并返回一个整型值。

整句话“int (*minus)(int,int) = subtraction;”是定义了这样一个指针并把函数subtraction的值赋给它,也就是说有了这个定义后minus就代表了函数subtraction。因此括号中的两个int int实际只是一种变量类型的声明,也就是说是一种形式参数而不是实际参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401836.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ 高级数据类型(四)—— 动态内存分配

到目前为止&#xff0c;我们的程序中我们只用了声明变量、数组和其他对象&#xff08;objects&#xff09;所必需的内存空间&#xff0c;这些内存空间的大小都在程序执行之前就已经确定了。但如果我们需要内存大小为一个变量&#xff0c;其数值只有在程序运行时 (runtime)才能确…

C++ 高级数据类型(六)—— 自定义数据类型

前面我们已经看到过一种用户&#xff08;程序员&#xff09;定义的数据类型&#xff1a;结构。除此之外&#xff0c;还有一些其它类型的用户自定义数据类型&#xff1a; 定义自己的数据类型 (typedef) C 允许我们在现有数据类型的基础上定义我们自己的数据类型。我们将用关键字…

JSF 2.0/2.1 生命周期简介

2019独角兽企业重金招聘Python工程师标准>>> 标准的生命周期划分为六个阶段&#xff1a;恢复视图、应用请求值、验证、更新模型值、调用应用程序、渲染响应&#xff0c;每一个阶段都可以直接跳转到最后一个阶段或者结束。 转载于:https://my.oschina.net/koulikoro/…

C++ 面向对象(一)—— 类(Classes)

类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class &#xff0c;其功能与C语言中的struct类似&#xff0c;不同之处是class可以包含函数&#xff0c;而不像struct只能包含数据元素。 类定义的形式是&#xff1a; [cpp] view plaincopy class cl…

Seen.js – 使用 SVG 或者 Canvas 渲染 3D 场景

Seen.js 渲染3D场景为 SVG 或者 HTML5 画布。Seen.js 包含对于 SVG 和 HTML5 Canvas 元素的图形功能的最简单的抽象。所有这个库的其它组件都是不用关心将要渲染的上下文的类型。 您可能感兴趣的相关文章你见过吗&#xff1f;9款超炫的复选框&#xff08;Checkbox&#xff09;效…

C++ 面向对象(三)—— 类之间的关系

友元函数(Friend functions) 在前面的章节中我们已经看到了对class的不同成员存在3个层次的内部保护&#xff1a;public&#xff0c; protected 和 private。在成员为 protected 和 private的情况下&#xff0c;它们不能够被从所在的class以外的部分引用。然而&#xff0c;这个…

五年后存储会是什么样子

原文&#xff1a;http://chucksblog.emc.com/chucks_blog/2013/06/what-storage-might-look-like-in-five-years.html注明&#xff1a;本文内容基于 VMware VSAN beta 版本撰写&#xff0c;请访问http://www.vmware.com/products/virtual-san/获得有关正式版本的更新信息。有时…

java.lang.NoClassDefFoundError: javax/transaction/Synchronization (jUnit测试报错)

测试hibernate报错原因项目缺少包在 hibernate 解压目录下找到 jta.jar 文件往项目中添加该 jar 包&#xff0c;即可解决添加方法&#xff1a;【右击项目】-->【构建路径】....来自为知笔记(Wiz)转载于:https://www.cnblogs.com/zhanyao/p/3711322.html

yarn oom问题一例

线上部分job运行失败&#xff0c;报OOM的错误:因为是maptask报错&#xff0c;怀疑是map数量过少&#xff0c;导致oom&#xff0c;因此调整参数&#xff0c;增加map数量&#xff0c;但是问题依然存在。看来和map的数量没有关系。通过jobid查找jobhistory中对应的日志信息&#x…

C++ 高级篇(三)—— 出错处理

本节介绍的出错处理是ANSI-C 标准引入的新功能。如果你使用的C 编译器不兼容这个标准&#xff0c;则你可能无法使用这些功能。 在编程过程中&#xff0c;很多时候我们是无法确定一段代码是否总是能够正常工作的&#xff0c;或者因为程序访问了并不存在的资源&#xff0c;或者由…

C++ 高级篇(一)—— 模板(Templates)

模板(Templates)是ANSI-C 标准中新引入的概念。如果你使用的 C 编译器不符合这个标准&#xff0c;则你很可能不能使用模板。 函数模板( Function templates) 模板(Templates)使得我们可以生成通用的函数&#xff0c;这些函数能够接受任意数据类型的参数&#xff0c;可返回任意类…

PPT到底是天使还是魔鬼?

说老实话&#xff0c;我非常不喜欢那些PPT的培训者一味地鼓吹PPT的重要性&#xff0c;视觉化思维的重要性&#xff0c;PPT在演讲中的重要性等等&#xff0c;我很清楚他们这么说是因为他们必须这么说&#xff0c;因为如果不把PPT的重要性强调一下&#xff0c;谁来上他的课呢&…

ultraedit正则表达式

一般使用ultraedit中的Perl风格的正则表达式&#xff0c;下面是perl正则的基本语法 perl中的元字符如下&#xff1a; ^ 表示一行的开头&#xff1b; $ 表示一行的结尾&#xff1b; ( ) 表示一个匹配块的&#xff0c;可以对匹配上的块通过$1,$2...进行读取&#xff0c;…

微软云介绍

微软云介绍 http://msdn.microsoft.com/zh-cn/ff380142 什么是云开发&#xff1f;&#xff08;概述&#xff09; 云计算是指远程运行并通过 Internet 访问的计算机和应用程序。在云计算中&#xff0c;虚拟机在大型数据中心中运行&#xff0c;并取代了物理 PC 和服务器。通过将许…

IO端口和IO内存的区别及分别使用的函数接口

IO端口和IO内存的区别及分别使用的函数接口 每个外设都是通过读写其寄存器来控制的。外设寄存器也称为I/O端口&#xff0c;通常包括&#xff1a;控制寄存器、状态寄存器和数据寄存器三大类。根据访问外设寄存器的不同方式&#xff0c;可以把CPU分成两大类。一类CPU&#xff08…

Win8 HTML5与JS编程学习笔记(二)

近期一直受到win8应用的Grid布局困扰&#xff0c;经过了半下午加半个晚上的奋斗&#xff0c;终于是弄明白了Grid布局方法的规则。之前我是阅读的微软官方的开发教程&#xff0c;书中没有详细说明CSS3的布局规则&#xff0c;自己鼓捣了半天也是一头雾水&#xff0c;于是又找到了…

Windows下的Qt Creator的安装

采用Qt和Qt creator分别下载和安装的方式&#xff1a;&#xff08;需要手动设置关联Qt和Qt Creator&#xff09; 一、软件下载 从http://qt-project.org/downloads分别下载Qt和Qt Creator&#xff1a; Qt使用4.7.2版本&#xff1a;qt-win-opensource-4.7.2-mingw.exe Qt Creato…

进程 、进程组、会话、控制终端之间的关系

一个进程组可以包含多个进程 进程组中的这些进程之间不是孤立的&#xff0c;他们彼此之间或者存在者父子、兄弟关系&#xff0c;或者在功能有相近的联系。 那linux为什么要有进程组呢&#xff1f;其实提供进程组就是方便管理这些进程。假设要完成一个任务&#xff0c;需要同时并…

Angularjs 通过asp.net web api认证登录

Angularjs 通过asp.net web api认证登录 Angularjs利用asp.net mvc提供的asp.net identity&#xff0c;membership实现居于数据库的用户名/密码的认证登录 环境 Vs.net 2013 Asp.net mvc web api Individual user accounts Angularjs Underscore 新建一个asp.net mvc web api …

PANIC: Unreachable code reached.

为什么80%的码农都做不了架构师&#xff1f;>>> Caused by: java.lang.RuntimeException: PANIC: Unreachable code reached.at cryptix.jce.provider.cipher.BlockCipher.engineGetParameters(BlockCipher.java:244)at javax.crypto.Cipher.checkCryptoPerm(Ciphe…