指针的概念
指针保存了一个内存地址,对指针的操作就是对地址的操作。
可以将内存理解为一个“大数组”,指针相当于存储了一个数组下标,它指向下标对应位置的变量。

指针的声明
&运算符被称为取地址运算符,它返回变量在内存中的地址。void指针可以指向任意类型的值。
int a;
int *ptrToA = &a;
double b;
double *ptrToB = &b;
void *voidPtrToA = &a;
void *voidPtrToB = &b;
取地址运算符不能作用于常量或表达式,如int *ptr = &2; 或者int *ptr = &(a + b); 。因为他们在内存中并没有固定的地址。
空指针
空指针的值可以用NULL(C和C++98的风格)或者nullptr(C++11新标准)表示。他们的值都是0x0,表示指针不指向任何对象。
//cite from <stddef.h>
#ifndef __cplusplus
#define NULL ((void *)0) // C语言
#else
#define NULL 0 // C++
#endif
可以发现,C语言的NULL保证为void*类型,但C++的NULL仅为常量0。在C++中使用NULL宏可能会导致函数重载错误。相比之下, C++11引入的nullptr始终保证其为指针类型。
在C++代码中,建议使用nullptr。
指针赋值
int *intp = &x; //让指针指向某一变量
int *intp1 = &x, *intp2;
intp2 = intp1; // 同类型的指针之间可以赋值,赋值相当于改变了指针指向的对象。
间接访问
为了访问指针指向的值,我们使用*符号,这被称为解引用运算符:
*ptr的值为指针指向的变量的值;*ptr的修改会作用到原对象上。注意声明int *ptr = &a;中的*并不是解引用运算符,它是类型声明的一部分。
void类型
void*指针可以指向任何类型的值。void*类型不可以解引用。事实上,使用void*代表着你放弃了所有类型检查和类型安全性。因此,除非必要,不建议在C++代码中使用void*类型。
int a = 233;
int *ptrToA = &a;
void *voidPtrToA = &a;
cout << *ptrToA << endl; // 233
cout << ptrToA << endl; //一个十六进制数,表示内存中的位置。例如0x7ffd99314e64
*ptrToA = 466;
cout << a << endl; // 466
*voidPtrToA = 699; // Compile Error: ‘void*’ is not a pointer-to-object type
int c = 1, *ptrToC = &c;
ptrToA = ptrToC; // 现在ptrToA指向了变量c
reinterpret_cast
对于一个指向类型A的指针,我们可以将其转换成一个指向类型B的指针。此时,指针指向的位置没有变,只是对于内存中数据的解释方式变了。
转换的方式便是B *ptr2 = reinterpret_cast<B *> ptr1;。
如以下代码,表示用一个float指针解释一个内存中的int变量:
int x = 1;
float *fp = reinterpret_cast<float *> &x;
要注意的是转换后的类型,它的有效长度不能比原来的类型更长。比如说int类型为 4 byte,double类型为 8 byte。将一个指向int类型的指针转换成指向double类型的指针,这在语法上没有问题,但是如果解引用得到的指针,double多出的 4 byte 的数据是无意义的。
数组名其实就是指向数组第0个元素的指针。但是,不能修改“数组名”这个指针的值,即它是常量指针。
int a[] = {1, 2, 3};
*a; // 等同于a[0]
*(a + n); // 等同于a[n]
int *p = a + 1;
*p; // 2
*(p + 1); // 3
p = p + 1; // *p == 3
p = p - 1; // *p == 2
cout << p - a << endl; // 1
// 定义一个指针,申请一块内存,地址存入指针,通过指针间接访问动态申请的内存
int *scores;
scores = 内存的起始地址;
申请动态变量
p = new type; // 申请动态变量
p = new type[size]; // 申请动态数组
p = new type(初值); // 申请动态变量并初始化
p = new int[5]{1,2,3,4,5}; //申请动态数组并初始化
释放动态变量的空间
// 动态变量的空间必须由程序释放
delete p; // 释放动态变量
delete [] p; // 释放动态数组
// 字符数组可以不加方括号
实例
int main()
{
int *p;
p = new int;
if (!p) {
cout << "allocation failure\n";
return 1;
}
*p = 20;
cout << *p;
delete p;
return 0;
}
new Type在堆(程序的静态存储区)上新建一个对象,返回指向这个对象的指针。new Type[cnt] 在堆上新建一个长为cnt,类型为Type的数组,返回这个数组的首指针。new运算符的初始化:
int *p1 = new int; // *p1为不确定的任意值
int *p2 = new int(3); // *p1 == 3
int *p3 = new int(); // *p1 被初始化为0
int *p4 = new int[4]; // p4数组的所有元素为不确定值
int *p5 = new int[4] {1, 2, 3, 4}; // 用花括号列表初始化
int *p6 = new int[4] (); // p6数组的所有元素被初始化为0
int *p7 = new int[4] {1}; // 注意:p7数组仅有第0个元素为1;其余元素都被初始化为0

char *String; // 严格说是错误的(编译器会提示warning,但可以运行),此时若修改字符串的任一位,行为未定义(undefined)。应为const char *String;
String = "abcde"; // 字符串常量的每一个字符不可以修改
char *String, ss[] = "abcdef";
String = ss;
char *String;
String = new char[10];
strcpy(String, "abc");
用指针处理字符串——编写一个统计字符串中单词的个数的函数
#include <ctype>
using namespace std;
int word_cnt(const char *s)
{
int cnt = 0;
while (*s != '\0') {
while (isspace(*s)) ++s; //跳过空白字符
if (*s != '\0') {
++cnt; //找到一个单词
while (!isspace(*s) && *s != '\0') ++s; //跳过单词
}
}
return cnt;
}
1.并没有交换成功:
void swap(int a, int b)
{
int c;
c=a; a=b; b=c;
}
2.用指针传递
void swap(int *a, int *b)
{
int c;
c=*a; *a=*b; *b=c;
}
int main()
{
int a = 1, b = 2;
swap(&a, &b);
}
如何让此函数返回二个跟——指针传递
函数原型
void SolveQuadratic(double a, double b, double c, double *px1, double *px2)
函数调用
SolveQuadratic(1.3,4.5,2.1,&x1,&x2)
SolveQuadratic(a,b,c,&x1,&x2)
两类函数参数
输入参数:用值传递
输出参数:用指针传递
在参数表中,输入参数放在前面,输出参数放在后面
如何获知方程有根、没根
让函数返回一个整型数。该整型数表示解的情况:
完整的函数
int SolveQuadratic(double a, double b, double c, double *px1, double *px2)
{
double disc,sqrtDisc;
if(a==0) return 3; //不是一元二次方程
disc = b * b - 4 * a * c;
if(disc < 0) return 2; //无根
if (disc == 0) {*px1 = -b/(2*a);return 1;} //等跟
//两个不等根
sqrtDisc = sqrt(disc);
*px1 = (-b + sqrtDisc) / (2 * a);
*px2 = (-b - sqrtDisc) / (2 * a);
return 0;
}
函数的使用
int main()
{
double a,b,c,x1,x2;
int result;
cout << "请输入a,b,c:";
cin >> a >> b >> c;
result = SolveQuadratic(a,b,c,&x1,&x2);
switch (result) {
case 0:cout<<"方程有两个不同的根:x1="<<x1<<"x2 = " << x2; break;
case 1:cout<<"方程有两个等根:"<<x1;break;
case 2:cout<<"方程无根";break;
case 3:cout<<"不是一元二次方程";
}
}
实例
原型设计
参数:从哪一个字符串中取子串、起点和终点
返回值:取出的子串
字符串可以用一个指向字符的指针表示,所以函数的返回值是一个指向字符的指针
char *subString(const char *s, int start, int end)
{
int len = strlen(s);
if (start<0||start>=len||end<0||end>=len||start>end) {
cout<<"起始或终止位置错"<<endl;
return NULL;
}
char *sub = new char[end - start + 2];
strncpy(sub, s +start, end - start + 1);
return sub;
}
调用subString的函数必须释放空间!!!
char *pc = subString(str, 3, 10);
out << pc << endl;
delete pc;
引用——给某个变量取一个别名,使一个内存单元可以通过不同的变量名来访问
定义格式:
类型 &变量名 = 变量名;
int i;
int &j = i;
j是i的别名,i与j是同一个内存单元。这个绑定关系在j的生命周期中不能改变
常量引用
const 类型 &变量名 = 变量名;
int i;
const int &j = i;
控制变量的修改,不管被引用对象是常量还是变量,用此名字是不能赋值
如i=5是合法的,j=5是非法的
const引用的初值可以是常量或表达式。如:
const int &y=2+5;√ int &y=2+5; ×
引入引用的主要目的是将引用作为函数的参数
引用可以理解为变量的“别名”。同时,也可以理解为一个type *const指针,即指针指向的对象的值可变,但指针本身的地址不可变。在指针的基础上,引用省略了取地址和解引用。
对引用的操作(求值,修改等)始终绑定在原对象上。
int a = 1;
int &b = a; // a == 1; b == 1
a = 2; // a == 2; b == 2
b = 3; // a == 3; b == 3
cout << sizeof(b) << endl; // 4; 和sizeof(a)相同
cout << &b << ' ' << &a << ' ' << (&a == &b) << endl; // true; a和b的地址是一样的
int *c = &b; // 此时c指向a
cout << sizeof(c) << endl; // 64位系统上为8; 和上面sizeof(b)做对比
// 指针参数
void swap(int *m, int *n)
{
int temp;
temp=*m;
*m=*n;
*n=temp;
}
swap(&x,&y);
// 引用参数
void swap(int &m,int &n)
{
int temp;
temp=m;
m=n;
n=temp;
}
swap(x,y);
以上引用传递相当于发生了如下变量定义:
int &m = x;
int &n = y;
按值传递大对象时,会导致对整个对象的拷贝,可能很慢;而按引用传递时,开销仅仅为传一个指针(64 位计算机中为 8 bytes)。
struct BigType
{
int val[1000];
};
void func1(BigType a) // copies 4000 bytes
{
// ...
}
void func2(const BigType &a) // copies 8 bytes
{
// ...
}
类型名 &函数名(形式参数表)int a[] = {1,3,5,7,9};
int &index(int); //声明返回引用的函数
void main()
{
index(2) = 25; //将a[2]重新赋值为25
cout << index(2);
}
int &index(int j)
{ return a[j]; } //函数是a[j]的一个引用
返回值必须是一个离开函数后依然存在的变量!!!
函数也可以返回变量的引用,此时和指针类似。可以返回的引用包括:
同上,不能返回局部变量的引用。
int glob;
int& func(int a, int &b, int *c)
{
int tmp;
return a; // err
return tmp; // err
return b; // ok
return *c; // ok: *c为引用类型
return glob; // ok
}