C++编程中指针的声明与基本使用讲解

内容摘要
使用以下序列声明指针。




[storage-class-specifiers] [cv-qualifiers] type-specifiers
[ms-modifier] declarator ;


其中,任何有效指针声明符均可用于 declarator。
文章正文

使用以下序列声明指针。

[storage-class-specifiers] [cv-qualifiers] type-specifiers 
[ms-modifier] declarator ;

其中,任何有效指针声明符均可用于 declarator。简单指针声明符的语法如下所示:

* [cv-qualifiers] identifier [= expression]

1.声明说明符:
可选存储类说明符。
应用于要指向的对象的类型的可选 const 或 volatile 关键字。
类型说明符:可表示要指向的对象的类型的类型名称。
2.声明符:
可选的 Microsoft 专用修饰符。

* 运算符。
应用于指针本身的可选 const 或 volatile 关键字。
标识符。
可选初始值设定项。
指向函数的指针的声明符类似于以下形式:

(* [cv-qualifiers] identifier )( argument-list ) [cv-qualifers]
[exception specification] [= expression];

对于指针数组,语法如下所示:

* identifier [ [ constant-expression ] ]

但是,指针声明符可能更复杂。
多个声明符及其初始值设定项可能同时出现在前面有声明说明符且以逗号分隔的列表中的一个声明中。
指针声明的简单示例如下:

char *pch;

前面的声明指定 pch 指向 char 类型的对象。
更复杂的示例是

static unsigned int * const ptr;

前面的声明指定 ptr 是一个指向 unsigned int 类型(带静态存储持续时间)的对象的常量指针。
下一个示例演示如何声明和初始化多个指针:

static int *p = &i, *q = &j;

在前面的示例中,指针 p 和 q 都指向类型 int 的对象并分别初始化为 i 和 j 的地址。存储类说明符 static 应用于这两个指针。

// pointer.cpp
// compile with: /EHsc
#include <iostream>
int main() {
 int i = 1, j = 2; // local variables on the stack
 int *p;

 // a pointer may be assigned to "point to" the value of
 // another variable using the & (address of) operator
 p = & j; 

 // since j was on the stack, this address will be somewhere
 // on the stack. Pointers are printed in hex format using
 // %p and conventionally marked with 0x. 
 printf_s("0x%p\n", p);

 // The * (indirection operator) can be read as "the value
 // pointed to by".
 // Since p is pointing to j, this should print "2"
 printf_s("0x%p %d\n", p, *p);

 // changing j will change the result of the indirection
 // operator on p.
 j = 7;
 printf_s("0x%p %d\n", p, *p );

 // The value of j can also be changed through the pointer
 // by making an assignment to the dereferenced pointer
 *p = 10;
 printf_s("j is %d\n", j); // j is now 10

 // allocate memory on the heap for an integer,
 // initialize to 5
 p = new int(5);

 // print the pointer and the object pointed to
 // the address will be somewhere on the heap
 printf_s("0x%p %d\n", p, *p);

 // free the memory pointed to by p
 delete p;

 // At this point, dereferencing p with *p would trigger
 // a runtime access violation.

 // Pointer arithmetic may be done with an array declared
 // on the stack or allocated on the heap with new.
 // The increment operator takes into account the size 
 // of the objects pointed to.
 p = new int[5];
 for (i = 0; i < 5; i++, p++) {
 *p = i * 10;
 printf_s("0x%p %d\n", p, *p);
 }

 // A common expression seen is dereferencing in combination
 // with increment or decrement operators, as shown here.
 // The indirection operator * takes precedence over the 
 // increment operator ++. 
 // These are particularly useful in manipulating char arrays.
 char s1[4] = "cat";
 char s2[4] = "dog";
 char* p1 = s1;
 char* p2 = s2;

 // the following is a string copy operation
 while (*p1++ = *p2++);

 // s2 was copied into s1, so now they are both equal to "dog"
 printf_s("%s %s", s1, s2);
}

输出:

0x0012FEC8
0x0012FEC8 2
0x0012FEC8 7
j is 10
0x00320850 5
0x00320850 0
0x00320854 10
0x00320858 20
0x0032085C 30
0x00320860 40
dog dog

另一个示例演示如何在数据结构中使用指针;本例中采用链接列表。

// pointer_linkedlist.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;

struct NewNode {
 NewNode() : node(0){}
 int i;
 NewNode * node;
};

void WalkList(NewNode * ptr) {
 if (ptr != 0) {
 int i = 1;
 while (ptr->node != 0 ) {
  cout << "node " << i++ << " = " << ptr->i << endl;
  ptr = ptr->node;
 }
 cout << "node " << i++ << " = " << ptr->i << endl;
 }
}

void AddNode(NewNode ** ptr) {
 NewNode * walker = 0;
 NewNode * MyNewNode = new NewNode;
 cout << "enter a number: " << endl;
 cin >> MyNewNode->i;

 if (*ptr == 0)
 *ptr = MyNewNode;
 else {
 walker = *ptr;
 while (walker->node != 0)
  walker = walker->node;

 walker->node = MyNewNode;
 }
}

int main() {
 char ans = ' ';
 NewNode * ptr = 0;
 do {
 cout << "a (add node) d (display list) q (quit)" << endl;
 cin >> ans;
 switch (ans) {
 case 'a':
  AddNode(&ptr);
  break;
 case 'd':
  WalkList(ptr);
  break;
 }
 } while (ans != 'q');
}

输出:

a
45
d
a
789
d
qa (add node) d (display list) q (quit)
enter a number:
a (add node) d (display list) q (quit)
node 1 = 45
a (add node) d (display list) q (quit)
enter a number:
a (add node) d (display list) q (quit)
node 1 = 45
node 2 = 789
a (add node) d (display list) q (quit)

固定和可变指针

const 和 volatile 关键字用于更改处理指针的方式。 const 关键字指定指针在初始化后无法修改;此后指针将受到保护,防止进行修改。
volatile 关键字指定与后跟的名称关联的值可由用户应用程序中的操作以外的操作修改。因此,volatile 关键字对于声明共享内存中可由多个进程访问的对象或用于与中断服务例程通信的全局数据区域很有用。
如果某个名称被声明为 volatile,则每当程序访问该名称时,编译器都会重新加载内存中的值。这将显著减少可能的优化。但是,当对象的状态可能意外更改时,这是保证可预见的程序性能的唯一方法。
若要将指针指向的对象声明为 const 或 volatile,请使用以下形式的声明:

const char *cpch;
volatile char *vpch;

若要将指针的值(即指针中存储的实际地址)声明为 const 或 volatile,请使用以下形式的声明:

char * const pchc;
char * volatile pchv;

C++ 语言会阻止将允许修改声明为 const 的对象或指针的赋值。此类赋值会移除用来声明对象或指针的信息,从而违反原始声明的意图。请考虑以下声明:

const char cch = 'A';
char ch = 'B';

假定前面声明了两个对象(const char 类型的 cch 和 char 类型的 ch),以下声明/初始化将是有效的:

const char *pch1 = &cch;
const char *const pch4 = &cch;
const char *pch5 = &ch;
char *pch6 = &ch;
char *const pch7 = &ch;
const char *const pch8 = &ch;

以下声明/初始化存在错误。

char *pch2 = &cch;  // Error
char *const pch3 = &cch;  // Error

pch2 的声明声明了一个可以用来修改常量对象的指针,因此不允许使用。 pch3 的声明指定 pointer 是常量,而不是对象;与不允许使用 pch2 的原因相同,也不允许使用该声明。
以下八个赋值显示了通过指针进行的赋值以及对前面的声明的指针值的更改;现在,假设 pch1 到 pch8 的初始化是正确的。

*pch1 = 'A'; // Error: object declared const
pch1 = &ch;  // OK: pointer not declared const
*pch2 = 'A'; // OK: normal pointer
pch2 = &ch;  // OK: normal pointer
*pch3 = 'A'; // OK: object not declared const
pch3 = &ch;  // Error: pointer declared const
*pch4 = 'A'; // Error: object declared const
pch4 = &ch;  // Error: pointer declared const

声明为 volatile 或 const 和 volatile 的组合的指针遵循相同的规则。
指向 const 对象的指针通常用于函数声明中,如下所示:

errno_t strcpy_s( char *strDestination, size_t numberOfElements, const char *strSource );

前面的语句声明了函数 strcpy_s,其中,三个参数中的两个是指向 char 的类型指针。由于参数是按引用而不是按值传递的,因此,如果未将 strSource 声明为 const,则该函数可以自由修改 strDestination 和 strSource。将 strSource 声明为 const 可向调用方保证调用的函数无法更改 strSource。
注意
由于存在从 typename * 到 const typename * 的标准转换,因此将 char * 类型的参数传递到 strcpy_s 是合法的。但是,反之则不行;不存在从对象或指针中移除 const 特性的隐式转换。
给定类型的 const 指针可以分配给同一类型的指针。但是,非 const 类型的指针不能赋给 const 指针。以下代码显示了正确和错误的赋值:

// const_pointer.cpp
int *const cpObject = 0;
int *pObject;

int main() {
pObject = cpObject;
cpObject = pObject;  // C3892
}

以下示例显示了当有指针指向某个指向对象的指针时如何将对象声明为 const。

// const_pointer2.cpp
struct X {
  X(int i) : m_i(i) { }
  int m_i;
};

int main() {
  // correct
  const X cx(10);
  const X * pcx = &cx;
  const X ** ppcx = &pcx;

  // also correct
  X const cx2(20);
  X const * pcx2 = &cx2;
  X const ** ppcx2 = &pcx2;
}


代码注释

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!