指针
基本概念:指针(pointer)是一个值为内存地址的变量(或数据对象)
声明及初始化指针变量
基本用法:数据类型 * 指针变量名
int * ptr_num;
char * ptr_name;
float * money_ptr;
注:
- int* p的写法偏向于地址,即 p 就是一个地址变量,表示一个十六进制地址
- int *p的写法偏向于值, *p是一个类型变量,能够表示一个整型值
- 声明中的 * 号和使用中的 * 号含义完全不同
空指针(null poiniter)
空指针:空指针不指向任何对象,在试图使用任何一个指针之前可以先检查是否为空,空指针指向的内存是不可以访问的。
用法:
int *ptr1 = nullptr//如果不赋初值,就是野指针
注意初始化所有指针,并在可能的情况下,尽量等定义了对象之后再定义指向它的指针。
void *指针:
一种特殊的指针类型,可以存放任意对象的地址
double num = 3.14;
double * ptr_num1 = #
void * ptr_num2 = #
cout << (ptr_num1 == ptr_num2) << endl;
注:
- void *指针存放一个内存地址,地址指向的内容是什么类型不能确定
- void *类型指针一般用来:拿来和别的指针作比较、作为函数的输入和输出;赋值给另一个void *指针
小结:
- 如果一个变量存储另一个对象的地址,则称该变量指向这个对象
- 指针变量可以赋值,指针的指向在程序执行中可以改变
注:
- 指针可以是任何基本数据类型、数组和其他所有高级数据结构的地址
- 若指针已声明为指向某种类型数据的地址,则它不能用于存储其他类型数据的地址
- 应为指针指定一个地址后,才能在语句中使用指针
- 0~255之间的内存编号是系统占用的,因此不可以访问
野指针:指针变量指向非法的内存空间
int main()
{
//指针变量p指向内存地址变量为0x1100的空间
int * p = (int*)0x1100;
//访问时报错:
cout << *p << endl;
}
const修饰指针
const修饰指针有三种情况:
- const修饰指针——常量指针
- const修饰常量——指针常量
- const既修饰指针,又修饰常量
int a = 1, b = 2;
const int * p = &a; //常量指针
特点:指针的指向可以修改,但是指针指向的值不能修改
*p = 2; //false
p = &b; //true
***********************************************
int * const p = &a; //指针常量
特点:指针的指向不可以修改,指针指向的值可以修改
*P = 3; //true
p = &b; //false
**********************************************
const int * const p = &a;//const既修饰指针,又修饰常量,都不能修改
引用
引用:为对象起了另外一个名字(引用即别名)
语法:数据类型 &别名 = 原名;
注:
- 引用并非对象,只是为一个已经存在的对象起的别名
- 引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起(int& ref_value = 10 ;//错误)
- 引用必须初始化,所以使用引用之前不需要测试其有效性,因此使用引用可能会比使用指针效率高。引用一旦初始化,就不可以在修改(例:a是b的别名,就不能再改成c的别名了)
- 将引用变量用作参数时,函数将使用原始数据,而非副本
- 当数据所占内存比较大时,建议使用引用参数
- 不可以直接引用常量,但是指向常量的引用是合法的:
double& d = 2;// d = 2 ,不合法,违背了常量的基本概念
//指向常量的引用--合法
const double& d = 2;
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参;
优点:可以简化指针修改实参
使用引用参数:
void Swap1(int, int);
void Swap2(int*, int*);
void Swap3(int&, int&);
int main()
{
int num1 = 10, num2 = 5;
Swap1(num1, num2);
cout << "执行Swap1后:" << num1 << '\t' << num2 << endl;
Swap2(&num1, &num2);
cout << "执行Swap2后:" << num1 << '\t' << num2 << endl;
Swap3(num1, num2);
cout << "执行Swap3后:" << num1 << '\t' << num2 << endl;
return 0;
}
void Swap1(int num1, int num2)
{
int temp;
temp = num1;
num1 = num2;
num2 = temp;
}
void Swap2(int* p1, int* p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void Swap3(int& ref1, int& ref2)//使用引用
{
//使用引用的理由:1.可以更加简便的书写代码
//2.可以直接传递某个对象,而不只是把对象复制一份
int temp;
temp = ref1;
ref1 = ref2;
ref2 = temp;
}
注:
- 引用参数中使用const可以避免参数被无意修改
- 引用参数建议尽可能的使用const
返回引用
函数返回引用类型;用法:函数调用作为左值
- 不要返回局部变量的引用
#include <iostream>
using namespace std;
int& sum();//返回引用类型的函数
int main()
{
int& Num = sum();
cout << Num << endl;//打印结果正确,因为编译器做了保留
cout << Num << endl;//结果错误,因为num的内存已经释放
return 0;
}
int& sum()
{
int num = 10;//函数中的局部变量会被内存回收
return num;
}
- 函数调用作为左值
int& test()
{
static int a = 10;//静态变量数据存放在全局区,在程序结束后系统释放
return a;
}
int main()
{
int& ref = test();
cout << ref << endl;
cout << ref << endl;
test() = 100;//如果函数的返回值是引用,这个函数调用可以作为左值
//test返回的是a这个变量,相当于a=100,ref是a的别名
cout << ref << endl;
}
- 函数可以不返回值,默认返回传入的引用对象本身
int& sum(int& num1, int& num2)
{
num1++;
num2++;
//默认返回最后一个更新的引用参数
}
int main()
{
int num1 = 10, num2 = 15;
int& result = sum(num1, num2);
cout << result << endl;
return 0;
}
- 返回引用时,要求函数参数中包含被返回的引用对象
小结:使用引用参数的一些指导原则
- 能够修改调用函数中的数据对象
- 数据对象较大时传递引用可以提高程序的运行效率
函数中不需要修改传递的参数:
①如果数据对象很小,建议按值传递
②传递数组只能使用指针,并使用const关键字
③较大的对象则使用const指针或引用,以提高程序效率
函数中需要修改传递的参数:
①数据对象是基本类型或结构时,可以使用指针或引用
②数据对象是数组时只能使用指针
③数据对象是类对象时,要求使用引用
引用的本质
引用过的本质在c++内部实现是一个指针常量
指针和引用:
- 引用对指针进行了简单的封装,底层仍然是指针
- 获取引用地址时,编译器会进行内部转换
int num = 100;
int& rel_num = num;
rel_num = 100;
cout << &num << '\t' << &rel_num << '\t' << rel_num << endl;
常量引用
使用场景:用来修饰形参,防止误操作
·
·
·
·
·
·
动态分配内存
使用new分配内存,使用delete释放内存,与new配对使用(不能释放声明变量分配的内存)
//1.在运行阶段为一个int值分配未命名的内存
//2.使用指针来访问(指向)这个值(右->左)
int * ptr_int = new int;
delete ptr_int; //释放由new分配的内存
注:不要创建两个指向同一内存块的指针,有可能误删两次
动态分配的数组
使用new创建动态分配的数组
int * intArray = new int [10];
new运算符返回第一个元素的地址
使用delete[]释放内存
delete[] intArray;//释放整个数组
关于new和delete使用的规则:
- 不要使用delete释放不是new分配的内存
- 不要使用delete释放同一内存两次
- 如果使用new[]c为数组分配内存,则对应delete[]c释放内存
- 对空指针使用delete是安全的
栈区(stack)
由编译器自动分配释放,一般存放函数的参数值、局部变量的值等;
操作方式类似数据结构中的栈— —先进后出
堆区(heap)
一般由程序员分配释放,若程序不释放,程序结束时可能由操作系统回收;
注:与数据结构中的堆是两回事,分配方式类似链表
全局区(静态区-static)
存放全局变量和静态变量以及常量,程序结束后由系统释放
文字常量区
存放常量字符串的地方,程序结束由系统释放
程序代码区
存放函数体的二进制代码,由操作系统进行管理的
#include <iostream>
using namespace std;
int main()
{
//栈区
int num2;
//栈区
char str[] = "空你急哇";
//栈区
char * ptr2;
//“空你急哇”以及'\0'在常量区,ptr3在栈区
char * ptr3 = "空你急哇";
//全局(静态)初始化区
static int num3 = 2048;
//分配的内存在堆区
ptr1 = new int[10];
ptr2 = new char[20];
//注:ptr1和ptr2本身是在栈区中的
return 0;
}
·
·
·
数组与指针案例:
#include <iostream>
using namespace std;
int main()
{
//定义数组实现,实现逆序
int num[]{12, 43, 10, 389, 8, 67};
int numCount = sizeof(num) / sizeof(num[0]);
int* p_num = num;
int* temp = new int;
for(int j = 0; j < numCount / 2; j++)
{
*temp = *(p_num + j);
*(p_num + j) = *(p_num + numCount - j - 1);
*(p_num + numCount - j - 1) = *temp;
}
cout << "逆序后:" << '\n';
for(int i = 0; i < numCount; i++)
{
cout << *(p_num + i) << endl;
}
return 0;
}