指针

指针是一个变量,存储的是值的地址

1
2
&Value => Address/Pointer
*Pointer/Address => Value

例如:

1
2
int myInt = 10;     //Value
int *myPoint = &myInt; //Pointer

上边的写法是将 myInt 的地址赋给了指针 myPoint ,前边的 * 可以和 int 一同理解为一个整体,表示一个整型的指针。

指针的声明

int *myPoint;
int * myPoint;
int* myPoint;

动态分配内存

  • 使用new来分配内存

    1
    typeName * pointer_name = new typeName;
  • 使用delete释放内存

    1
    delete pointer_name;

总结:使用 * 声明指针,指向某值的地址,使用 &Value 获得值的地址,使用 new 会动态分配空间,使用 delete 释放动态分配的空间

指针与数组

使用new创建动态数组

动态数组表示在运行过程中创建。如果不使用 new 来声明数组,那么数组会在编译的时候就被分配相应的内存,不论是否有值都会占据相应的存储空间,成为静态联编(static binding),类似于使用值存储;使用 new 来声明数组,数组会在程序运行时创建,成为动态联编(dynamic binding),类似于使用链表存储。

动态数组的创建与释放

1
2
typeName * pointer_name = new typeName [num_elements];
delete [] pointer_name;

动态数组的使用

pointer_name 为指向数组第一个值的指针,调用数组第n个元素可以:

  • pointer_name[n];
  • pointer_name+n; pointer_name[0];

数组名与指针

数组名即为指向该数组第一个元素的指针,即存在:

1
arrayName = &arrayName[0]

所以以下两个情况等价:

1
2
3
4
int myInt[3] = {0,1,2};
int* myPoint=myInt; <==> int* myPoint = &myInt[0]
myPoint[0] <==> *myPoint
myPoint[2] <==> *(myPoint+2)

但是只有指针才可以有运算,数组名不可以有运算,例如在上边的代码中,myPoint+1 是合法的,但是 myInt+1 是不合法的

指针算术

将指针加一,等于将指针增加的量指向其类型的字节数。比如一个指向double类型的指针加一,其数值其实增加8,以指到下一个地址去。

还可以将一个指针减去另一个指针,获得两个指针的差。这种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种运算才有意义;这将得到两个元素的间隔。

指针和字符串

在C++的 cout 以及其他多数表达式中,char数组名、char指针以及用引号引起来的字符串常量都被解释为字符串中第一个字符的地址。

由于数组名是数组中第一个元素的指针,所以在字符串数组中,如果直接打印数组名,将会一直打印所有的内容,直到出现\0才结束,如:

1
2
char myChar[10] = "hello";
cout << myChar;

上述代码将打印 hello

1
2
3
char myChar[10] = "hello";
char* myPointer = myChar;
cout << myPointer;

上述代码将打印 hello

1
2
char myChar[10] = "hell\0o";
cout << myChar;

上述代码将只打印 hell

指针与结构

使用 new 可以创建动态结构,使用指针调用动态结构中的某一项需要使用 ->,(使用结构名来调用某一项使用的是 .

1
2
3
4
5
6
7
8
9
struct myStruct
{
int myInt;
double myDouble;
};

myStruct * myPointer = new myStruct;
myPointer->myInt = 10;
myPointer->myDouble = 10.2;

存储方式

根据用于分配内存的方法,C++管理数据内存的方式可以分为 自动存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。(C++11新增了线程存储)

  • 自动存储:
    在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),它们在所属的函数被调用时自动产生,在该函数结束时消亡。自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。自动变量通常存储在栈中,即执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量。
  • 静态存储:
    静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字 static 定义。
  • 动态存储:
    使用 new 和 delete 运算符可以提供一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间(free store)或堆(heap)。该内存池同用于静态变量和自动变量的内存是分开的。因此,数据的生命周期不完全受程序或函数的生存时间控制。

指针与const

对于将const应用于指针,这个问题是很微妙又复杂的,因为存在两种情况:

  • 将const作用于指针指向的值,使指针指向一个常量对象,这样可以防止使用指针来修改其指向的值,但是指针所指的位置可以随意更改,即可以更换指针指向的变量,但是那个const的变量本身是无法修改的
  • 将const作用于指针本身,这样可以防止改变指针指向的位置,但是该位置所存储的值是可以改变的,即可以通过指针指向的变量修改该地址存储的值

指针指向常规变量

1
2
int myInt=10;
const int * pt = &myInt;

这里的pt是一个指向 const int(10)的指针,因此无法使用pt来修改这个值,即 *pt 的值为const,不能被修改,如 *pt+=1 是不行的。

但是在这里,myInt本身并不是const的常量,而是对于pt而言,这个值是常量,也就是说无法利用pt来修改myInt的值,但是可以直接修改myInt本身:

1
2
*pt = 20;     // 不合法的
myInt = 20; // 合法的

但是这种情况下,可以将一个新的地址赋给pt,这就也很微妙,虽然pt无法改变其所指向的地址存储的值,但是可以改变其所指向的地址,比如可以将一个新的地址赋给pt

1
2
int anotherInt = 30;
pt = &anotherInt;

尽管改了新的地址,但是由于pt本身是一个指向常量的指针,所以依然无法改变新的地址存储的值(这里的30)

常规指针指向变量

1
2
int myInt=10;
int* const pt = &myInt;

当const的位置改变,这种情况下,表示pt指针是一个const的常规指针,它只能指向myInt而不能改变指向的地址,也就是不能再对其赋一个新的值,但是由于myInt本身是一个变量,所以可以通过myInt改变10这个值,也可以通过pt来改变这个值。

1
2
*pt=20// 合法的
pt=&anotherInt; // 不合法的

指向常规对象的常规指针

1
2
int myInt = 10;
const int* const pt = &myInt;

这是前两种情况的组合,此时pt只能指向myInt,并且pt不能用来修改myInt的值,即 pt*pt 都是const。

常规指针指向常规变量

1
2
const int myInt=10;
const int* pt = $myInt;

此时既不能使用myInt更改10这个值,也不能使用pt来修改10这个值。

一种不允许的情况

1
2
const int myInt = 10;
int* pt = &myInt;

这种情况是不允许的,因为如果这个赋值成立,那么const的状态就很奇怪了,myInt本身是常量,却可以使用pt指针来修改,所以这种情况是不合法的

指针、数组与函数的参数传递

我们知道,函数对于传递数组作为参数,根据函数是否有权限改变数组的值,通常有两种声明方式:

1
2
void functionModify(int arr[], int size);	// 1号函数
void functionNoChange(const int arr[], int size); // 2号函数

此时,如果我们有两个数组:

1
2
cosnt int array1 = {1,2,3};	// 1号数组
int array2 = {4,5,6}; // 2号数组

很明显,一个是常量数组,一个是变量数组。两种数组对于两个函数的关系是这样的:

  • 禁止将常量数组的地址赋给非常量指针,即:1号数组不能传递给2号函数,只能传递给1号函数
  • 可以将非常量数组赋给常量或非常量指针,即:2号数组可以被传递给1和2号函数

因此,在设计函数时,要尽可能地使用const,因为将指针参数声明为指向常量数据的指针可以:

  • 避免由于无意间修改数据而导致的编程错误
  • 使用const使得函数能够处理const和非const实参,否则将只能接受非const数据

所以,如果条件允许,则应将指针形参声明为指向const的指针