Hardwired 的指针 - C 方法






2.60/5 (13投票s)
2005年2月23日
10分钟阅读

48908
指针入门 - 如果你完全不知道它们是什么以及如何使用它们,请阅读此文!
引言
指针可能是 C 语言中最有用和最聪明的特性之一。那些不明白这一点的人,根本不知道什么是编码(我一直有一个定义:“编码”=“用 C 或 C++ 语言编程”)。指针是一个非常强大的工具,可以用于各种方式。关于这一点,你将在这个材料中了解更多。
前提
我将从最常见和已知的定义开始:指针是一个类型变量,其中存储着一个对象的内存地址。这意味着这个变量中包含的值是一个地址。现在,不要想到那些你经常在各种开源代码中看到的十六进制值,比如 0x440562。
假设第三个值 156 是变量 Var
的值。那么,我们有
int Var; Var = 156;
Var
的值存储在地址 102。现在,我们将声明一个指针
int *pVar;
下一步是给这个指针赋值。如果我们假设我们知道 Var
的值存储的内存地址,那么我们可以这样做
pVar = 102; // this is a correct assignment only in case we know the address
这完全不推荐,因为在大多数情况下你不知道地址。像上面那样的代码行,如果你在类 Unix 系统上编译并运行它,会给你一个“段错误”的消息。你可以这样做
pVar = &Var;
符号“&
”的翻译是“地址”。所以,这意味着 pVar
= Var
的地址。现在,在 pVar
变量中,我们有值 102。我们现在可以这样说:“pVar
指向 Var
”,这意味着 Var
的地址包含在 pVar
变量中。下一步是访问 pVar
指向的地址中存储的值。我们通过使用“*
”符号来做到这一点
int NewVar; // another variable NewVar = *pVar;
现在,我们将 pVar
指向的地址中存储的值赋给了 NewVar
变量。所以,“*
”的翻译是“地址处的值”。输出
printf( "value of NewVar = %d", NewVar );
将是
value of NewVar = 156
同样,您也可以使用 pVar
指针为 Var
变量赋值。这是因为 Var
存储的地址不会改变,并且 pVar
指向那个地址
*pVar = 173;
所以,“地址处的值” pVar
现在是 173,内存将看起来像这样
指针的类型
指针有不同的类型。如前所述,指针是一个包含内存地址的变量。但是存储在该内存地址中的值可以是特定类型,例如 double
、int
或 char
。这就是为什么有不同类型的指针。
int SomeVar; // a variable of int type int *pSV; // a pointer pSV = &SomeVar;
在上面的例子中,我们将 SomeVar
的地址赋给了 pSV
。为了能够做到这一点(并且不会收到难看的错误消息或警告——取决于编译器),我们必须声明:int *pSV
,这意味着 pSV
将指向一个 int
类型的值。
切勿尝试执行类似以下操作
float AnotherVar; // avariable long *pAV; // a pointer pAV = &AnotherVar; // !!! no way !!!
这意味着在 pAV
中,您将存储一个 float
变量的地址。但是 pAV
的类型是 long
,而 ANSI 标准告诉我们,这样的操作是不可能的。
指针运算
有两种运算类别
- 指针运算。
- 对指针指向的值进行运算。
最后一类运算大家已经知道了,因为它们与对普通变量进行运算相同
double AVar; // first variable double BVar; // second variable double *pA; // first pointer double *pB; // second pointer AVar = 20; // we assign a value to first variable BVar = 12; // we assign a value to the second variable pA = &AVar; // make pA to point to AVar address pB = &BVar; // same for pB // // now we'll do several operations over the values of AVar and BVar: // (*pA)++; (*pB)--; (*pA) -= (*pB); printf( "value of AVar = %lf\n", *pA ); printf( "value of BVar = %lf\n", *pB );
输出将是:
value of AVar = 10
value of BVar = 11
现在,让我们看看我们可以对指针进行哪些运算,或者更确切地说,对存储在指针中的地址进行运算。但是,在我们开始之前,我们需要定义“地址”这个词。为了定义它,我们需要了解内存的表示以及操作系统如何处理它。
内存可以“看作”一个字节数组(1 字节 = 8 位)。这对于几乎所有的计算机来说都是如此,这种表示与物理、经济限制和实际原因有关。这个数组的尺寸等于你的计算机可能拥有的物理内存总量。这不仅仅指你安装的 RAM,还包括一些额外的内存,如显存和交换空间。所以,内存是一个字节数组,它的元素可以通过指定索引来访问。这个索引称为地址。这里 comes the first restriction
你 **不能** 给指针赋负值(将其视为地址,而不是该地址中包含的值)。这是因为内存从 0 开始,到你的计算机所拥有的任何值结束。
但这并不是唯一的限制。由于内存还被其他进程(应用程序或操作系统管理操作)使用,你不能给指针赋随机值,因为你很可能会导致系统崩溃(MS-DOS、Win 95、'98、ME)或者你的应用程序会崩溃(类 Unix 系统和 WinNT 系列)。
int *pC; // some pointer pC = 15; // assign some address (*pC) = 200; // !!! now, you've done it !!!
所以,最好让系统来管理内存。你只需要处理它。
现在,我们可以回到这个主题的重点:指针运算。指针运算不多:有四种:“+
”、“++
”、“-
”、“--
”。你可以将两个指针相加,递增一个指针,用一个指针减去另一个指针,递减一个指针。为了更好地理解,我们来看一个例子
float A; float B; float C; float *pA; float *pB; float *pC; A = 100; // assign some value B = 150; // same thing C = 14; // blah blah pA = &A; // you already know what this does pB = &B; pC = &C; pC = pA + pB; // add two pointers (1) pC++; // increment pointer(2) pA = pA - pB; // subtract(3) pB--; // decrement(4)
- 这里我们将两个指针
pA
和pB
相加,结果存储在pC
中。这意味着我们将指针pA
中的地址与指针pB
中的地址相加,结果存储在pC
中。如果你对结果地址一无所知,这是一件非常危险的事情。这仅用于举例,你需要理解这样的运算是允许的,但你必须知道如何在安全的情况下使用它。 - 我们可以递增一个指针。以下解释也适用于第 4 种情况。当我们递增一个地址时,我们必须注意指针的类型。对于
char
类型指针的情况——一个char
变量存储在 1 个字节中。所以,一个char
指针将指向一个char
值。如果我们要递增指针,那么它将指向下一个char
值。这意味着如果指针中的地址是,比如说,100;而我们递增它,新值将是 101。对于long
或float
类型指针来说,情况并非如此。对于大多数操作系统(类 Unix 和 Windows 套件),long
和float
类型变量使用 4 个字节存储。现在,假设我们有一个float
指针 = 100。如果我们递增它,新值将是 104。在开始使用指针编写应用程序之前,理解这一点非常重要。另外,当你使用指针时,你可能想知道存储特定类型变量所需的字节数。你可以使用sizeof()
函数找出这一点。 - 在这种情况下,规则与情况 1 相同。此外,你必须注意不要得到负地址值。这意味着被减去的指针的值不能小于减数指针的值。
- 正如我之前所说,情况 2 中的规则也适用于这种情况。此外,我们必须检查递减后指针是否会变成负值。如果是,则不允许该操作。另外,地址是一个整数值,你不能对指针进行诸如加或减非整数值这样的运算,如下例所示
double *pD; // a pointer pD = pD - 100.32; // !!!wrong!!!
请注意,该指针是
double
类型。这并不意味着地址是double
类型。这意味着该地址中包含的值是double
类型。
独立指针
这不是一个正确的术语,但我认为在这个情况下使用它是合适的。在以上所有主题中,我只解释了我们将指针指向已定义变量的地址的情况。但是,当我们不为指针分配任何地址时,我们可以用指针做什么?它们如何使用?
如前所述,指针的值是一个地址。该地址可以是一个存在的地址,比如一个已定义变量的地址。你也可以为其分配内存并使用指针。这意味着你不再需要为其分配地址……而是为其分配一个自己的地址。这样,你将像使用它本身一样使用指针,而无需定义任何不必要的变量。
int *pVar; // // allocate momory for the pointer // why? because this pointer does not POINT to an already existing // data, so, we at least could kind enough to give it some memory // to work with:) // pVar = (int*)malloc( sizeof(int) ); *pVar = 56;
通过说“分配内存”,你应该理解系统将在内存中的某个位置为指针分配一些空间,并将该位置的地址自动赋给指针。malloc()
函数完成了这一切。如果此操作过程中出现任何问题,则赋给指针的地址将为 NULL
,这意味着 **没有地址**。你可以使用以下方法检查这一点
pVar = (int*)malloc( sizeof(int) ); if( NULL == pVar ) // !!!this is not required, but highly recommended!!! return <some error>; // if yes, return or do something about it, // anyway, DO NOT work with the pointer!!! *pVar = 56;
……这样,你就为下一个主题做好了准备
数组指针
在 C 语言中,指针和数组之间存在一种内在的关联,这两种实体几乎可以互换。这种关系可能是一个最强大和独特的工具。当你使用数组而不带索引时,你实际上是在生成一个指向数组第一个元素的指针,因此编译器会实际处理所有内容,直到遇到第一个 NULL
。
char szMessage[] = "this manual is great!"; //:) printf( "%s\n", message );
在上面的例子中,我将一个 char
数组传递给了 printf()
。函数接收第一个元素的地址,通过将其转换为 char
指针。这是因为在 C 语言中,无法将数组传递给函数。你只传递它第一个元素的地址。即使你不知道,你也已经使用了指针。因为数组名在不带索引的情况下代表第一个元素的地址,你可以将这个地址赋给一个指针,通过它就可以访问数组中的任何元素。这类指针被称为算术指针。
int Array[ 6 ] = { 1, 2, 3, 12, 54, 5 }; int *pArray; int i; // assign to the pointer the address // of the array's first element pArray = Array; // // the following code is the classic method // of accessing the elements of the array: // for( i = 0; i < 6; i++ ) printf( "%d\n", Array[ i ] ); // // now, use the arithmetic pointer: // for( i = 0; i < 6; i++ ) printf( "%d\n", *(pArray + i) );
结构指针
一种常用的 C 技术是通过指针访问结构。声明一个指向结构的指针的方法与上面描述的其他数据类型一样。
struct SomeStruct { int AVar; // a int member of the structure double BVar; // a double member } AStruct, *pAStruct;
AStruct
是一个类型为 struct SomeStruct
的变量,而 pAStruct
是一个指向该类型结构的指针。所以,现在我们允许将 pAStruct
指向 AStruct
变量的地址。
pAStruct = &AStruct;
要使用指针访问结构的成员,我们将不得不使用箭头运算符“->
”。
pAStruct->AVar = 12;
这意味着我们使用 pAStruct
指针将值“12”赋给了结构 AStruct
的 AVar
成员。就像上面描述的所有其他类型的指针一样,这个指针也非常重要。比如说,你将一个结构作为参数传递给一个函数。 C 编译器会这样做:将整个结构传输到被调用的函数。当有非常大的结构时,比如你在 WinAPI 中看到的那些,可能会导致大量的内存和处理时间损失。这时就用到了结构指针。如果你通过指向它的指针将该结构传递给函数,编译器就只会传输结构的地址。在非常复杂的应用程序中,这类技术至关重要。
结论
一位智者曾说过:“当我们厌倦思考时,我们就会匆忙下结论”。C 语言之所以成为最强大的非面向对象语言之一,就是因为指针。它们提供了对内存资源的底层访问,以及在复杂应用程序中大量提速的能力。我希望这个材料让你理解了指针的力量。如果你至少正确地使用它们一次,我相信你会明白为什么我如此喜欢它们。
关注点
好吧……如果你以前完全不知道指针是什么,那么,希望你已经从这个材料中学到了。
历史
- 版本 1.0 - 2005 年 2 月 23 日 - 此材料三年前发布在另一个网站上。但那个社区的活动太少了,所以我现在不喜欢它们了 :)。