C++ 指针与引用
- C++中的指针主要是管理地址信息
- 管理数据
- 调用代码
1. 指针定义与基本操作
- 定义:
<基类型>*<指针变量>
:void*
:可以作为所有指针的接口,void的指针类型可以被赋值为任何类型的指针。
int a = 9;
int* p = &a;
int* q = p;//指向同一地址
*p = 8;
void* p1 = p;
double* q1;
p1 = q1;//是允许的
- 使用typedef来定义一个指针类型(别名)
typedef int* Pointer;
// p和q均为指针变量
Pointer p, q;
//等价于
int*p, q;//主要q是int不是指针
- 可以直接进行赋值:因为C++可以进行系统开发,所以一定是可以操作绝对地址的。
int *p = (int *)0x080483A0;
int *p = 0x080483A0;
1.1. 基本操作
- 取地址:
&
- 间接取内容:
*
int x=9;
int *p;
p = &x;
*p = 1000;
- 所有的指针都要初始化(Pointer Literal)
- C++会初始化指针为0(默认初始化),如果编译器发现指向为0,则报错,因为0地址是保留空间
- 不允许:
char *p = (void*)
- 在新的C++部分中,我们引入了
nullptr
:作为不依赖任何值的指针。Pointer p = nullptr;
//ANSI C
#define NULL ((void*)0)
//C++
#define NULL 0
//以下的情况,会调用int的重载版本
void func(int);
void func(char*);
func(NULL);
- 空指针并不一定用与整数0同样的二进制模式表示,可由实现者采用任何选定的方式表示。
- 赋值:同类型赋值:
p = &d//error,不同类型
- 加减:整形
- 结果类型:不变
- 数值:sizeof(基类型) * 整形数值
- char*是一个一个走
int* p ;
double *q;
//注意这里的++隐含的意义是加上一个sizeof(type)
p++;//p的值加4 (sizeof(int))
q++;//q的值加8 (sizeof(double))
1.2. 指针之间的运算
- 同类型指针相减(仔细看offset的定义)
- 结果类型:整形
- 数值:偏移量
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int *p = &a[0];
for (int i = 0; i < 10; i++) {
cout << *p++ << " ";//指针移动
cout << *p << " ";
cout << *(p + i) << endl;//指针不动
for (int j = 0; j < 10; j++) {
cout << a[j] << " ";
}
cout << endl;
}
- 同类型指针比较:
- == 或者 !=
- 一般不使用 > 等符号
int x=1;
int *p=&x;
cout << p; // p的值 (x的地址)
cout << *p; // p所指向元素的值
char *p = "ABCD";//有问题
char *p = (char *) "ABCD"//没有问题,但是并不推荐这么使用
cout << p;//p指向的字符串,即: ABCD
//调用ostream& operator << (ostream&, char*)
//调用时,operator << (cout,p);
cout << *p; //p指向的字符,即:A
cout << (int *)p //p的值
- void*
- 只管理地址信息
void *p;
- 是指针类型的公共接口
- 任何操作须做强制类型转换(不然是没有意义的)
void *any_pointer;
int x;
double y;
any_pointer = &x;
any_pointer = &y;
*any_pointer //error,对void*类型的指针取值的时候,一定要先转换为对应具体类型的指针后再进行取值
*((int *)any_pointer) //OK
*((double *)any_pointer) //OK
- 指针可用来将某块内存清零
//例:将某块内存清零,按照bit进行处理!
void memset ( void *pointer, unsigned size) {
char *p = (char *)pointer;
for (int k=0;k<size;k++)
*p++ = 0;
}
void memcpy(void *des, void *src, unsigned size) {
//进行内存拷贝
char *sp = (char *)src;
char *dp = (char *)des;
for (int i = 0; i < size; i++) {
*dp++ = *sp++;
}
}
void showBytes(void *q, int n)//查看内存
{
cout << n << endl;
unsigned char *p = (unsigned char *)q;
for (int i=0; i<n; i++){
cout << (void *)(p+i) << " : "<< setw(2) << hex << (int)*(p+i) << " ";
if ( (i+1) %4 ==0 ) cout << endl;
}
cout << dec << endl;
}
1.2.1. memset()的部分具体解释
- 通常是为申请内存进行初始化的操作
- 可以将int数组的空间初始化为0或者-1
- 函数原型:
memset(void *s,int ch,size_t n);
struct A{...};
A a;
memset(&a,sizeof(A));
int A[100];
memset(&a[0],100);
int arr[100] = {0};
memset(arr,sizeof(arr));
memset(arr,100 * sizeof(int));//arr作为参数传递时
1.3. 常量指针与指针常量
- 操作地址一定要保证存在并且有意义
1.3.1. 常量指针
const <类型> * <指针变量>
- 不可以修改指针指向单元的内部的值
const int c = 0;
const int *cp;
int y = 1;
int *q;
cp = &c; √//cp 可以指向 c
q = &y; √//q 可以指向 y
*cp = 1 ; ×//*cp 是一个常量,不可以赋值
*q = 2 ; √//变量指针可以指向变量
cp = &y; √//常量指针可以指向变量,传递的是y的空间,并且对于y的这个空间只是可读的,安全的
q = &c; ×//不可以的,因为q的修改可以间接修改c,所以编译器不允许
void print(int *p){
cout << *p << endl;
}
const int c = 8;
print(c) ;//不可以被调用的
print(&c);//C++赋给的权利,在调用的时候除去常量的特性,这个&是强制类型转换,取消常量特性
void print(const int *p){//如此修改就可以大量复用
//常量使用者和变量使用者都可以使用
cout << *p << endl;
}
- 常量指针指向的地址存储的值不可以被修改,用来消除函数的副作用,保证在函数端中只读数据。
- cp(variable) -> c(constant)
- 服务提供者Use const whenever possible(cp = &y可以保证函数不修改参数中的值):让调用者直接访问被调用者空间中的数据,为了保证不可以修改数据,使用const
void Fun1(int *p){
//*p 读写
}
void Func2(const int *p){
//*p 只读
}
- 面向对象中没有const会带来很大的访问权限的问题
1.3.2. 实例说明指针
int x=10;
int *p = &x;
cout << " x " << &x << x << endl;
cout << " p " << &p << p << endl;
cout << "*p " << p << *p << endl;//*p = x
//Name Addr Value
//x 0012FF7C 10
//p 0012FF78 0012FF7C
//*p 0012FF7C 10
const int c = 128;
int * q = const_cast<int *>(&c);//强制类型转换
*q = 111;//企图通过变量指针修改常量
cout << " c " << &c << c << endl;
//这里的c是符号常量,所以在编译的时候,符号常量已经变为128了,相当于define
cout << " q " << &q << q << endl;
cout << "*q " << q << *q << endl;
//Name Addr Value
//c 0012FF74 128
//q 0012FF70 0012FF74
//*q 0012FF74 111
//why?为什么这个单元对于c是128,而对于q这个单元是111,见上面,确实已经修改成111了
void showBytes(void *q, int n)//查看内存
{
cout << n << endl;
unsigned char *p = (unsigned char *)q;
for (int i=0; i<n; i++){
cout << (void *)(p+i) << " : "<< setw(2) << hex << (int)*(p+i) << " ";//这里是很重要的
if ( (i+1) %4 ==0 ) cout << endl;
}
cout << dec << endl;
//cout利用控制符dec、hex和oct,分别输出十进制、十六进制和八进制显示整数
}
1.3.3. 指针常量
<类型>* const<指针变量>
- 在定义时初始化
- p(constant)->x(variable)
int x,y;
int *const p = &x;//p就始终如一的指向x这个单元
//同时这个单元是可变的
p = &y;//错误的
- const int * const p是非常强的指针约束
2. 指针与函数
- 指针作为形参
- 提高传输效率
- 函数副作用
- 常量指针
- 程序基本组织单位就是函数
- 进阶:Function Pointer指向函数的指针
int A[2];
typedef int T[2];//相当于int[2] T
double (*fp)(int);//fp是指向函数的指针
double (int) * fp;//上面的理解,不能这么写
double *fp (int);//符合C++语法,fp是一个函数,参数是int,返回值是double*
typedef double (*FP)(int);
typedef double (*)(int) FP;//上面那个的理解
double f(int x){}
int g(){}
void main(){
FP fp;
fp = f; //相当于fp = &f;为函数指针赋值
(*fp)(10);//相当于fp(10);
fp = g; //Error
}
- (*fp)就是函数的执行
2.1. 函数指针实现框架(如何写一个框架)
- 一个计算任务的执行(加法/减法)
- 是一个前缀输入
2.1.1. 第一版:高耦合版本
#include <iostream>
using namespace std;
int add(int a,int b) {return a+b;}
int minus(int a,int b) { return a-b; }
void main(){
char c;
int op1, op2;
cin >> c;
while (c != '#'){//#是终止符
//类似Windows中的一些时间的参数
//以下对应getTask()
cin >> op1;
cin >> op2;
//以下对应executeTask()
switch (c){
case '+': cout << add(op1,op2) << endl;
break;
case '-': cout << minus(op1,op2) << endl;
break;
}
cin >> c;
}
}
2.1.2. 第二版:剥离IO部分
//剥离IO getMessage,和操作系统一样
struct Task{
int op1;
int op2;
OPRAND_TYPE op;
};
enum OPRAND_TYPE { END=-1, ADD, MINUS};
int add(int a,int b) { return a+b; }
int minus(int a,int b) { return a-b; }
//add 和 minus 抽象成函数指针
typedef int (*FP)(int, int);
OPRAND_TYPE getTask(Task &task){
char c;
cin >> c;
switch (c){
case '#':
task.op = END;
break;
case '+':
task.op = ADD;
cin >> task.op1;
cin >> task.op2;
break;
case '-':
task.op = MINUS;
cin >> task.op1;
cin >> task.op2;
break;
}
return task.op;
}
2.1.3. 第三版:抽离计算部分
//抽离计算部分第一版
//如何修改可以使得无论多少个任务都不导致如下方法的修改
void executeTask(const Task task){
FP fp;
switch(task.op){
case ADD: fp = app;break;
case MINUS : fp = minus;break;
}
fp(task.op1,task.op2)
}
//抽离计算部分第二版代码
//Table Driven
FP op[2] = {add, minus};
void executeTask(const Task task){
op[task.op](task.op1,task.op2);
}
- 此时发生修改,我们只需要修改枚举类型和函数类型
2.1.4. 最后一版:主方法集成
void main()
{
Task task;
while (getTask(task) != END)
executeTask(task);//call by reference
}
//组织改善:利用define,集合IDE
//完成时间处理、协议解析、服务框架
2.2. 函数指针实现泛型
2.2.1. 冒泡排序第一版:默认int型排序
//第一版实现冒泡排序,默认数据类型为int
void MySort(int A[],unsigned int num)
{
for (unsigned i=1;i<num;i++){
for (unsigned j=0;j<num-i;j++)
if(A[j] > A[j+1]){
int tmp = A[j];
A[j] = A[j+1];
A[j+1] = tmp;
}
}
}
2.2.2. 冒泡排序第二版:扩展复杂数据类型
- 每一个数据块的大小可能是不确定的,所以我们需要确定每一个块的大小(width)
- void * base对应首地址
- 解决序关系的处理
- 解决数据块的交换
void MySort(void *base, unsigned width,unsigned num,int(*compare)(const void *elem1,const void *elem2)){//这部分意味着我们必须要传入一个compare的函数
char *A = (char*) base;//void* 是不可以进行移动的
char *tmp = (char*)malloc(width);//申请堆空间
for (unsigned i=1;i<num;i++){
for (unsigned j=0;j<num-i;j++)
if (compare(A + j * width,A + (j+1)*width) > 0){//序关系由函数确定
memcpy(tmp,A + j * width,width);//tmp = A[j]
memcpy(A + j * width,A+(j+1)*width,width);//A[j] = A[j+1]
memcpy(A + (j + 1) * width,tmp,width);//A[j + 1] = tmp
}
}
free(tmp);//释放这部分的空间
}
2.2.3. 冒泡排序第三版:使用泛型函数实现调用部分
struct TStudent
{ char name[20];
int age;
};
TStudent student[] = {...};
int num = sizeof(student)/sizeof(student[0]);//计算出来有多少个
int width = sizeof(student[0]);//计算出来宽度
MySort(student, width, num, icompare);
MySort(student, width, num, scompare);
//compare不用给大小,因为compare是调用者给出的,显然不用给出width了
//call back function:在运行中反过来调用
int icompare(const void *elem1, const void *elem2){
TStudent *p1 = (TStudent *)elem1;
TStudent *p2 = (TStudent *)elem2;
return p1->age - p2->age;
}
int scompare(const void *elem1, const void *elem2){
TStudent *p1 = (TStudent *)elem1;
TStudent *p2 = (TStudent *)elem2;
return strcmp(p1->name, p2->name);
}
2.2.4. 冒泡排序另一种实现:简单数据类型
template <class T>
void MySort(T A[],unsigned T num)
{
for (unsigned i=1;i<num;i++){
for (unsigned j=0;j<num-i;j++)
if(A[j] > A[j+1]){
T tmp = A[j];
A[j] = A[j+1];
A[j+1] = tmp;
}
}
}
int a[100];
sort(a,100);//此时的T转换成为int(对应类型)
class C{...}
C a[300];
sort(a,300);//编译器可以将其变为C,但是有问题
//我们需要重载>运算符
2.2.5. lambda表达式
直接给出即可
2.3. 函数指针
- 计算一元函数在某区间上的定积分
#include <math.h>
double integrate(double (*f)(double),double a, double b)
{ … f(x), a , b, … }
double my_func(double x){ … }
void main(){
integrate(sin,0,1);
integrate(cos,1,2);
integrate(my_func,1,10);
}
2.3.1. 一维数组
- 注意右侧的第二个部分:可以控制p的移动情况
-
*(p+i)
:p不移动 -
*(p++)
:p移动 int *p = a
:这时候a表示的是数组的首地址
- 这里传递的是
int * const
- a[0]可以写为p[0]
void f(int A[],int n){
sizeof(A)/sizeof(A[0])//始终1,就是地址
}
-
sizeof(a)
:是数组的整个块的大小 -
sizeof(a[0])
:是数组中一个元素的大小
2.3.2. 二维数组
- 二维数组用一维方式访问
-
int *p = &a[0][0]
:p指向的是T类型
for(int i = 0;i < 12;i++){
*(p++) = 9;//越界了(对应一维数组的越界),但是二维数组没有越界
}
typedef int T[2];
T a[6];//int a[6][2]
T *q = a;
//不使用T的方法
int[2] *q;
3. 指针与数组
- 数组元素操作:下标表达式和访问效率
- a[i] == *(a+i)
- &a[i] == a+i
int a[10];
sizeof(a);//数组大小
sizeof(a+1);//内存地址的长度,单位bytes
int *p;
int i=0;
p = &a[0] == p = a;
a[i] == *(a+i) == *(p+i) == p[i]
&a[i] == a+i == p+i == &p[i]
- 多维数组
int b[20][10];
//等价于
//typedef int T[10];
//T b[20];
int *q;
q = &b[0][0];// q = b[0]
//b[i][j] == *(&b[0][0] + i*10 + j) == *(q + i * 10 + j) == q[i*10 + j]
T * p;//int (*p)[10];
p = &b[0];// p = b
//b[i][j] == *(*(b+i)+j) == *(*(p+i)+j) == p[i][j]
- 通过指针和数组元素存储的关系来快速访问数组元素
3.1. 降维操作
- 越界操作:C++认为是允许的,只要这块内存空间在我们的控制范围内即可
#include <iostream.h>
int maximum(int a[], int n)
{
int max = 0;
for(int k=0;k<n;k++)
if (a[k] > max)
max = a[k];
return max;
}
void main()
{
int A[2][4] = { {68,69,70,71} , {85,86,87,89}};
cout << "the max grade is" << maximum(A[0],2*4);//maximum(&A[0][0],2*4) =>maximum(&A[0][0],sizeof(A)/sizeof(A[0][0]))
}
3.2. 升维操作(重要)
- 因为申请内存空间的时候只能申请到线性部分
void show(int a[], int n){
for (int i=0;i<n;i++) {
cout << a[i] << " ";
cout << endl;
}
cout << endl;
}
void show(int a[][2], int n){
for (int i=0;i<n;i++)
for (int j=0;j<2;j++) {
cout << a[i][j] << " ";
cout << *(a+i)+j << " :" << a[i][j] << " ";
//四个换一行
if ((i*2+j+1)%4 == 0)
cout << endl;
}
cout << endl;
}
void show(int a[][2][3], int n){
for (int i=0;i<n;i++)
for (int j=0;j<2;j++)
for (int k=0;k<3;k++){
cout << a[i][j][k] << " ";
cout << *(*(a+i)+j)+k << " :" << a[i][j][k] << " ";
//换行输出
if ((i*6+j*3+k+1)%4 == 0)
cout << endl;
}
cout << endl;
}
void main(){
int b[12];
for (int i=0;i<12;i++) b[i] = i+1;
show(b,12);
//二维数组
typedef int T[2];
show((T*)b,6);//show((int (*)[2])b,6),一定有括号
//三维数组
typedef int T1[3];
typedef T1 T2[2];
show((T2*)b,2);//show((int (*)[2][3])b,2)
}
3.3. 指针数组
- main函数:
int main(int argc,char * argv[],char * env[])
- argc:参数个数(包含命令)
- argv:命令行参数
- env:环境参数(为什么这个不必指出长度?因为\0结束,一个结束符)
- Eg.
ping -t 192.168.0.1
argc : 3
argv: ping / -t / 192.168.0.1
env:
- 数组中的元素为指针(以下两种方式实现是不同的:内存空间的分配)
char *s1[] = {"C++", "PASCAL", "FORTRAN"};
char s2[][8] = {"C++", "PASCAL", "FORTRAN"};
3.4. 可变参数
-
int printf(const char*,...)
:后面是可变参数,由调用者决定。 -
const char*
:是调用者和被调用者之间的约定
- printf("%d%c",x,y);
- 少写一个也没问题
- 这种约定是不受保护的,给出参数个数和类型,表示如何取
- active frame:之前的active frame地址要保存下来
3.5. 实现Myprint
- alignment的说明(内存地址)
目标求Q
X = Qn + r, -n < r <= 0 Q大于X,能放下,并且是整数倍
思考:X = qn + r, 0 <= r < n
q = x/n
r = x%n
这样子就能求了
X + n - 1 = Qn + r1, 0 <= r1 < n
Qn = ((x + n - 1)/ n) * n
n 是 2 的幂次 => 左移右移都是乘以或者除以2
n = 2 的 m 次方
所以先乘以2再除以2,相当于后m为全部清0
也就等价于(x+n-1) & (~(n-1))
- 具体的内存C++实现
//platform : x86 宏的说明,这不是在库文件中已经定义了的
typedef char *va_list;
#define _INTSIZEOF(x) ((sizeof(x) + sizeof(int) - 1) & ~(sizeof(int) - 1)) //alignment 偏移的大小
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap) ( ap = (va_list)0)
#include <iostream>
#include <stdarg.h>
using namespace std;
void MyPrint(char *s, ...){
va_list marker;//拿到一个指针
va_start(marker,s);//找到参数的位置,s的位置
int i=0;
char c;
while ((c=s[i]) != '\0'){
if (c != '%')
cout << c;
else{
i++;
switch (c=s[i]){
case 'f': cout << va_arg(marker,double); break;
case 'd': cout << va_arg(marker,int);break;
case 'c': cout << va_arg(marker,char);break;
}
}
i++;
}
cout << endl;
va_end(marker);//将当前指针回归原始状态
}
int max(int num, ...) {
va_list marker;//拿到一个指针
va_start(marker, num);
int maxNum = 0;
int tmp = 0;
for (int i = 0; i < num; i++) {
tmp = va_arg(marker, int);
if (tmp > maxNum) {
maxNum = tmp;
}
}
va_end(marker);//将当前指针回归原始状态
return maxNum;
}
void main(){
MyPrint("double: %f integer: %d string: %c ",1.1, 100, 'A');
cout << max(5,10,20,50,30,40);
}
- 格式化串攻击:偷摸摸搞到其他部分的内存
4. 指针与结构
- 结构成分的访问:
(*p).x == p->x
- 结构作为函数参数:
- 大块数据传输
- const
5. 多级指针
- 基类型为指针类型
- 指向指针的指针
- 编写一个函数交换两个字符串
void myswap(int *p1, int *p2) {
int* tmp = p1;
p1 = p2;
p2 = tmp;
}
void myswap2(int &p1, int & p2) {
int tmp = p1;
p1 = p2;
p2 = tmp;
}
void myswap(char **p1, char **p2) {
char *tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void main()
{
char *p1 =(char*) "abcd";
char *p2 =(char*) "1234";
int a = 100;
int b = 200;
myswap(&a, &b);
cout << a << " " << b << endl;//100 200
myswap2(a, b);
cout << a << " " << b << endl;//200 100
myswap(&p1, &p2);
cout << p1 << " " << p2 << endl;//1234 abcd
}
6. 动态变量
- 动态:
- 大小
- 生命周期
- 非编译时刻确定
- 是在heap中申请存储空间
6.1. 申请动态变量
new <类型名> [<整型表达式>]
- malloc也可以用来申请动态变量(但是建议使用new)
- new和malloc两者区别:
- 语法:强制类型转换
- 语义:构造函数
- 申请内存的时候有可能会申请失败:
- new之后一定要判断p是不是NULL
- 如果不是NULL,一定是有效的
//--------------------------------------
int x;
int *p = new int;
delete p;
int *p = (int *)malloc(sizeof(int));
free(p);
int &a = p;
6.1.1. 使用malloc分配空间
//malloc
void * malloc (unsigned int size)
p = (int *)malloc(sizeof(int)); //new int
q = (int *)malloc(sizeof(int)*20); //new int [20]
6.1.2. 分配连续空间(涉及多维数组)
//---------------------------------------
int *p = new int[10];//分配一块连续空间
int *p = (int*)malloc(sizeof(int)*10)
int (*p2)[5] = (int (*)[5])p;
for (int i=0;i<10;i++)
p[i] = i+1;
q = new int[2][5];//错误的,没有这种写法
//想用二维数组访问,升维操作
for (int j=0;j<2;j++)
{ for (int k=0;k<5;k++)
cout << p2[j][k] << " ";
cout << endl;
}
//---------------------------------------
//多维数组使用构造数据类型申请内存
typedef int i5Array [5];
void main(){
i5Array *p = new i5Array [2];
for (int j=0;j<2;j++)
for (int k=0;k<5;k++)
p[j][k] = (j*5)+(k+1);
}
6.1.3. 面向对象中的new关键字
//---------------------------------------
class A
A *p = new A;//调用默认构造函数
A *p = (A*) malloc(sizeof(A));//只是分配空间
//---------------------------------------
namespace std{//处理内存
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}//不满意应对,我们可以重载方法来处理
6.2. 归还动态变量
- 操作符:
new -- delete|delete[]
- delete:调用数组内第一个对象的析构函数
- delete[]:调用数组内所有的对象的析构函数
- 空间都会被归还
- 操作符:
malloc -- free
- free不会调用析构函数。
- 如何处理归还的大小(cookie):在数据的前面会加入一个size:这也就是为什么我们一定要复制指针,然后归还地址归还的是原地址。
int *p = new int[8];
for(int i = 0;i < 8; i ++){
*(p++) = 128;
}
delete[] p;
//很大的问题,因为p移动过,这时候指针想上看size:128,就向下归还128个字节。
- 由于C++没有GC,所以要防止memory leak
- 析构函数:不仅仅是归还自己的内存,还有窗口资源和文件等东西归还掉。
6.3. 动态变量的应用
- 数据结构:
- 链表(单、双) --栈、队列
- 树、图
- 链表的结点的定义
struct NODE{
int content;
NODE *next;
};
NODE *head=NULL;//使用头结点
- 具体应用:硬盘上的文件存放:一种实现是单链表
- 文件分配表FAT:用来存储数据的开始的位置。
- FAT一旦被破坏就导致所有的数据丢失
6.4. 单链表 - 应用
6.4.1. 单链表的插入
//节点初始化
NODE *p = new NODE;
p->content = _value;
p->next = NULL;
head是不可以动的
- 表头进行插入
//链表为空
head = p;
//链表不为空
p->next = head;
head = p;
- 表尾进行插入
//表尾插入
NODE *q = head;
while (q->next != NULL)//从头结点找到尾结点
q = q->next;
q->next = p;
- 表中间插入:插在链表中某结点(值为a)的后面
- 短路表达式:如果部分子表达式的值已经能确定表达式的值,则其他部分不会进行计算
NODE *q = head;
while (q != NULL && q->content != a ){
q = q->next;
}
if (q != NULL){
//存在a
p->next = q->next;
q->next = p;
}else{
//不存在a
cout << "Not found!";
}
- 表中间插入:插在链表中某结点(值为a)的前面
- 链表永远不为空(永远不发生在头的插入)
- Guard node:(一个Dummy结点在最前面)
//插在链表中某结点(值为a)的前面
NODE *q1=NULL, *q2=head;//q1是q2的前一个结点
while(q2 !=NULL && q2->content != a){
q1 = q2;
q2 = q2->next;
}
if (q2 != NULL){//存在a
if(q1 == NULL){// a是第一个结点
p->next = q2;
head = p;
}else{// a不是第一个结点
p->next = q2;
q1->next = p;
}
}else{//不存在a
cout << "Not found!";
}
6.4.2. 单链表的删除
- 删除值为a的链表结点
NODE *q1=NULL, *q2=head;//q1是q2前面的一个结点
while (q2 != NULL && q2->content != a){
q1 = q2;
q2 = q2->next;
}
if (q2 != NULL) {//存在a
if (q1 == NULL){
// a是第一个结点
head = q2->next;
delete q2;
}else{// a不是第一个结点
q1->next = q2->next;
delete q2;
}
}else{
//不存在a
cout << "Not found!";
}
6.5. 单向排序链 – 应用
- 结点定义
struct Node{
int k;
Node *next;
} *first = NULL;
6.5.1. 释放单向排序链
void release(){
//释放整个单向排序链
while(first != NULL){
Node *p = first;
first = first->next;
delete p;
}
}
6.5.2. 打印单向排序链
void print(){
//打印整个单向排序链
Node *p = first;
while (p){
cout << p->k << endl;
p = p->next;
}
}
6.5.3. 插入单向排序链
insert(Node *first, int n);
void insert(int k){
Node *p = new Node;
p->k = k;
p->next = NULL;
//创建新结点
if (!first){
//链表为空
first = p;
}else if (k < first->k){
//插入在头结点
p->next = first;
first = p;
}else{
//插入在后面
Node *p1 = first;
while (p1->next != NULL && k > p1->next->k)
p1 = p1->next;
p->next = p1->next;
p1->next = p;
}
}
//first作为main里面的局部变量,如下使用会有问题吗
void main(){
Node* first = NULL;
insert(first,n);//有问题,值传递,不能修改first
insert(&first,n);//这样子就行了
}
6.5.4. 删除单向排序链
void delNode(int k){
if (!first) return;
Node *p1 = first;
if (k == first->k){
//删除头结点
first = first->next;
delete p1;
}//删除头结点
else{
while(p1->next != NULL&& p1->next->k != k)
p1 = p1->next;
if (p1->next != NULL){
Node *p = p1->next;
p1->next = p->next;
delete p;
}
}
}
7. C++引用
- 定义:为一块已有的内存空间取一个别名
- 引用变量和被引用变量,必须是同类型
- 引用变量定义中的&不是取地址操作符
- 定义引用变量时,必须初始化
int &a = *p;//一旦是p的别名,就一定只能是p的别名了
void f(int &a)//利用函数副作用
- 应用:
- 函数参数传递
- 动态变量命名
- 函数返回值为指针或者引用
- 不可以返回局部量
- 涉及到操作符的重载
int max1(int x[], int num){
int m,i;
m = x[0];
for (i=1; i<num; i++)
if (x[i] > m) m = x[i];
return m;
}
int &max3(int x[], int num){
int i, j;
j = 0;
for (i=1; i<num; i++)
if (x[i] > x[j]) j = i;
return x[j];
}
int *max2(int x[], int num){
//返回的指针
int *p,*q;
p = x;
q = x+1;
while (num > 1){
if (*q > *p) p = q;
q++; num--;
}
return p;
}
int main(){
int A[16];//操作的是调用者的空间的部分
cout << max1(A,16);
cout << max2(A,16);//返回的是一个地址
*max2(A,16) = -1;//将最大值修改为-1
cout << max3(A,16);
max(A,16) = -1;//将最大值修改为-1
}
- 用 const 限定引用
void swap(const int& a, const int& b)
- 引用一旦定义,不可被改变,可以被const限制
- 及时释放在堆中的变量的引用
int *p = new int(100);
int &x = *p; …… ;
delete &x;