指针
在学习指针时,首先要知道()的优先级高于 [] , 且 [] 的优先级高于*
明确这一点,可以对包含指针的语句分析时,抽丝剥茧,快速理解复杂的语句。
A.B |
A为对象或者结构体,访问A的成员B,若A是指针则不适用 |
A->B | A为指针,->是成员提取,A->B是提取A中的成员B,且A只能是指向类,结构的指针 |
:: | 作用域运算符,A::B表示作用域A中的名称B, A是命名空间,类,结构 |
: | 表示继承 |
:: | 作用域解析 |
() | |
[] | 数组 |
-> | 成员访问 |
++ | 后缀自增 |
-- | |
! | 非 |
~ | 位非 |
+ | 正,加 |
- | |
++ | 前缀自增 |
-- | |
& | 地址 |
* | 接触引用(指针) |
- 指针是一个变量,存储的是地址信息
- P的值是地址
- *P的值是指向的数据值,完全可以把*P看成一个普通变量
- 获取地址值&
- 对于普通变量,想要获得该变量的地址,需要用地址运算符&,即&a表示的就是变量a的地址,和P一个意思
- *运算符也被称为是解除引用运算符
- 声明指针
- 指针的声明必须,指定指向的数据类型,如 int * p;
- 这里的*p 的数据类型是int,
- p是指向int类型的指针
- int *p 强调*p是个int类型,把 *p 看成普通int类型变量
- int* p强调int*是一种类型,即p是指向int类型的指针,强调的是int* ,把int*看成一种数据类型
- 指针的声明必须,指定指向的数据类型,如 int * p;
double * p; //定义声明了一个指向double类型的指针p
double* p; //定义声明了一个指向double类型的指针p,强调p是指向double类型的指针变量
double *p; //定义声明了一个指向double类型的指针p,强调*p是double类型
- 指针初始化
- Question: 初始化的是指针地址还是指针指向的数据值?
- 初始化的是指针存储的地址
- int *p = &a ;
- Question: 初始化的是指针地址还是指针指向的数据值?
- 指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
- &运算符注意点
- 对数组名进行&操作时,得到的是整个数组的地址,而不是第一个元素的地址(数组名=第一个元素的地址,无需&运算符),虽然这两个地址是相同的,但是操作起来时会有明显差异,如下:
- &array[2]和&array得到的是一个2字节的内存块的地址值,而&array则是一个20个字节内存块的首地址
- 存储
- 自动存储
- 在函数内部定义的常规变量使用自动存储空间,这些变量也叫自动变量,其使用的存储空间成为栈
- 自动变量其实是局部变量,随函数调用诞生,结束消亡
- 栈即LIFO后入先出
- 静态存储
- 程序执行期间都存在的存储方式,可以通过函数体外定义它,也可以通过声明变量时使用关键字static
- 只要程序活着,静态变量就一直存在
- 动态存储
- 运算符new和delete管理一个内存池,相当于自动存储的栈,但这里叫做堆,栈和堆事独立分开的
- 动态存储在程序运行时按需分配内存,用完就删除
- 自动存储
- 模版类vector
- vector也是一种动态数组,同样是new和delete管理内存的,但vector是自动完成的
- vector<typeName> name(number);
- vector<typeName> name;
- 指针和运算符
- 指针算术上述已有所述,这里结合自增运算符
- *++p,这里表示先把p的地址值增加1个单位的字节数,再结合*解除运算符
- *p++,同理
- ++*p,这里是把*p直接看成普通变量,再结合++自增运算符
- (*p)++ 这里是把*p看成普通变量
指针 vs 数组
指向数组的指针
牢记下边两个恒等式 arr为数组名,p为数组名地址值赋值的指针, p=arr
左边是数组表示法,右边是指针表示法
arr[i] == *(p+i)
&arr[i] == p + i
- 一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组(就是上述两个式子)
- 指针和数组并不是完全互换的,数组名相当于一个指针常量,其值为数组0号元素的首地址。
- 不可改变数组名的值,但指针变量可以进行运算
- 指针运算符 * 应用到 数组arr上是完全可以的,但修改arr存储的值是非法的,只能修改var代表的数组的数组成员的值。
- 例外:sizeof运算符对数组名取长度,返回的是整个数组长度
#include <iostream> using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; int *ptr; ptr = var; for (int i = 0; i < MAX; i++) { *var = i; // 这是正确的语法 var++; // 这是不正确的 ptr++; //TRUE, 指针加1个int单位字节,从而指向了下一个元素 } return 0; }
- 数组名等于第0个元素的首地址
- arr和&arr[0]相同
- arr和&arr虽然值相同,但是arr是第0个元素首地址,而&arr是整个数组首地址
- 当赋给指针进行算术运算时,前者将增减一个元素内存字节大小,而后者将增减一个数组字节大小
- 创建一个指向数组的指针
- 下边代码中:
- pa是个指针,指向数组0元素首地址
- pb是个指针,和pa相同
- pbb是个指针,与pa,pb同值,但pbb指向的是整个数组
- pc是个指针,指向一个包含了100个double的数组
- pd是个数组,该数组包含了100个double*指针元素
- 下边代码中:
double arr[100];
double *pa = arr;
double *pb = &arr[0];
double *pbb = &arr;
double (*pc)[100];
double *pd[100];
new运算符分配内存
- 在C语言中,使用库函数malloc()来分配内存,在C++中也兼容,但是推荐使用new运算符来分配内存
- new运算符的操作流程:
- ①在运行阶段为一个int值即int类型的数据,该数据没有名字,即无内存标签,为这个数据来分配未命名的内存(这个int类型数据是占内存空间的,只不过没有为这个内存空间打标签)
- ②分配未命名的内存,同时利用指针访问这个内存块
- ③我只需要告诉new,我需要创建一个什么类型的数据即可
- ④new运算符会自动找到一个合适的内存块,并返回内存块的地址
- ⑤总而言之,我只需要告诉new数据类型即可,new会把一个对应的内存块地址交给一个指针
- int * ptr = new int;
- 前后两个数据类型要对应,这里是int
- Q:指针初始化和new分配内存有什么区别呢?
- int * p = &a; 指针初始化,可以通过非指针变量来访问数据,即通过a来修改内存块中的数值,也可以通过*p来修改
- int * ptr = new int; new分配内存只能通过*ptr来修改访问数据
- Q:对地址你了解多少呢?
- 地址是内存块的名字,变量则是内存块的标签
- 注意:地址只是内存块的第一个bit的名,比如x01010101,如果是int类型,那么加32就是该内存块的最后一个bit的地址名
- new运算符来创建动态数组
- 当数据量很大时,应避免使用常规数组,字符串,结构等,应尽量使用new运算符来创建动态数组
- 场景: 当你写程序时,发现不确定是否使用数组,那么用不用数组全看输入什么数据,这时候就需要了解一下静态联编和动态联编的问题
- 静态联编: 在编译时分配内存,不管程序是否调用数组,都会占据相应的内存空间
- 动态联编: 在编译时不分配内存,即在运行时根据程序是否调用而创建相应大小的内存空间,创建的长度和实际长度相同,这种特殊的数组叫做dynamic array动态数组\
- int *ptr_dynamic_array = new int[11];
- []表示元素个数
- new返回数组第一个元素的地址
- 使用完毕时,要使用delete运算符释放数组,直接delete ptr_dynamic_array
- 动态数组的使用,只需要把指针当作数组名来用即可,比如ptr_dynamic_array[10]
- new运算符的补充
- 通过new运算符可以创建动态数组,也可以创建动态结构和动态类,原理和new创建动态数组一样,即在运行时按需分配内存块,返回内存块首地址
- inflatable * ps = new inflatable;
- *ps是个inflatable结构类型的指针,inflatable*和int*一个道理
- 这里补充一下,如果你的指针是个结构,那么成员的访问就需要用成员运算符->来访问
指针算术
- 指针+1,这里+1表示增加1个指针指向类型的内存字节数,比如int * p; p+1表示p存的地址值增加8个字节
- 数组,地址,指针
short tell[10]; cout<<tell<<endl; cout<<&tell<endl; cout<<&tell[0]<<endl;
- 变量指针可以递增,数组不能递增,数组可以视为一个常量指针。
- 数组名是其第一个元素的地址值,指针变量存放的也是地址值,所以指针变量可以当作数组名使用。
- 对数组名进行&取地址,需要注意:
- 对数组名取地址,&tell得到的是整个数组的地址,即得到的是一个数组所占内存块的块地址,&tell
- 对数组名取地址后,&tell 所可以赋值的指针,则是指向了一个包含了10个元素的数组
- 将&tell + 2那么,地址必然增加的是20
- 对数组第一个元素取地址,&tell[0] 就是一个元素所占内存块的块地址,往往默认为是数组名tell
- 把数组名tell看成指针,由于tell就是&tell[0],那么tell就是一个指向short类型的指针常量,
- 将tell+1,那么地址增加2个字节
- 对数组名取地址,&tell得到的是整个数组的地址,即得到的是一个数组所占内存块的块地址,&tell
- 因此对于数组和指针,既可以使用数组表示法,也可以使用指针表示法。
int main(){
double * ptr = new double;
ptr[1] = 100.21;
cout<<*(ptr+1)<<endl;
return 1;
}
// 100.21
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// 指针中最后一个元素的地址
ptr = &var[MAX-1];
for (int i = MAX; i > 0; i--)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;
cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;
// 移动到下一个位置
ptr--;
}
return 0;
}
Address of var[3] = 0xbfdb70f8
Value of var[3] = 200
Address of var[2] = 0xbfdb70f4
Value of var[2] = 100
Address of var[1] = 0xbfdb70f0
Value of var[1] = 10
int *pt = new int [10];
*pt = 5;
pt[0] = 6;
pt[9] = 44;
int coats [10];
* (coats +4) = 99;
//解释:
//coats表示数组第一个元素地址,+4则地址增加4个int内存空间,即移动到coats[4]的位置
//*(coats+4) 和 coats[4] 等价
辨析:
short (*pas)[20];
short *pas[20];
int *ar2[4];
int (*ar2)[4];
//解析
//牢记优先级() [] *
short (*pas)[20]; //pas是指针,指向由20个short元素组成的数组的指针
short *pas[20]; //pas是个数组,包含了20个指向short类型指针的数组
int *ar2 [4]; //ar2是个数组,表示由4个int指针组成的数组
//-1-参考int arr[4];表示arr是个int数组,长度为4
//-2-int* ar2[4];表示ar2是个int*数组,长度为4,只不过元素都是只想int的指针
int (*ar2) [4];//ar2是个指针,指向一个包含了4个int的数组
//-1-先看括号里面,(*ar2),表示ar2是个指针
//-2-再看括号外面,int ()[4];表示名为()的int数组,长度为4
指针和const
- const表示常量,对于指针而言,有两个目标,一个是指针存的地址值,另一个是指针指向的变量值
- 直接举栗子说明
// NO.1 right
int age = 30;
const int * p = &age; //无法通过p修改age,age自身可修改
// NO.2 right
const int age = 30;
const int * ptr = &age; //无法通过ptr修改age,age自身不可修改
// NO.3 false
const int age = 30;
int *ptr = &age; //可通过ptr修改age,age自身不可修改,矛盾,ERROR
- 接下来辨析如下四种类型,主要是指针常量, 指针变量,和常量,变量之间的关系辨析
- int类型的实体不能用const int来初始化,反之可以
#include <iostream>
using namespace std;
int main() {
/*当对象是变量或者常量时,思考const的存在或不同位置,能否导致指针自身和指针指向的对象 改变?*/
int num = 10; //当对象是int变量
int test = 1;
int * num_ptr0 = # // TRUE, *num_ptr0,可修改,num_ptr0可修改,num可修改
const int * num_ptr1 = # // TRUE, *num_ptr1常量不可修改,num_ptr1可修改,num可修改
int * const num_ptr2 = # // TRUE, *num_ptr2可以修改, num_ptr2不可修改,num可修改
const int* const num_ptr3 = # // TRUE, *num_ptr3常量不可修改, num_ptr3常量不可修改,num可修改
const int price = 100; //当对象是int常量
int * price_ptr0 = &price; //ERROR, 指针指向类型和指向对象类型不匹配, nt类型的实体不能用const int来初始化,反之可以
const int *price_ptr1 = &price; //TRUE,
int * const price_ptr2 = &price; //ERROR, 指针指向类型和指向对象类型不匹配, int类型的实体不能用const int来初始化,反之可以
const int* const price_ptr3 = &price; //TRUE
return 0;
}
- 在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
- 如需检查一个空指针,您可以使用 if 语句,
#include <iostream>
using namespace std;
int main ()
{
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ;
return 0;
}
ptr 的值是 0
if(ptr) /* 如果 ptr 非空,则完成 */
if(!ptr) /* 如果 ptr 为空,则完成 */
函数指针
指向函数的指针
- 获取函数的地址
- 直接使用函数名(注意区分函数名和函数返回值),如下,think为函数名,think()为函数返回值。
process(think);
process(think());
- 声明函数指针与初始化赋值
- 牢记优先级() [] * 的顺序为从高到低
- 需指定函数的返回类型、特征标(直接将函数声明的函数名用指针替换即可,如下)
double pam(int);
double (*f)(int);
f = pam;
- 函数指针的调用
- 直接把(*f)或者f作为函数名pam来用,都可以,注意(*f)的括号()
double pam(int);
double (*f)(int);
f = pam;
double x = pam(4);
double y = (*f)(4);
double z = f(4);
- 辨析
double pam(int);
double *f1(int);
double (*f2)(int);
int arr[10];
int *p = &arr[0];
int *a = arr;
int *b = &arr;
int *c[10] = &arr;
int (*d)[10] = &arr;
上述代码中:
f1是个函数,返回值类型是个double* 指针
f2是个指针,指向了一个函数,类型就是double (*)(int);
p是个指针,指向数组arr的0号元素首地址
a是个指针,和p一样(数组名arr就是0号元素首地址)
b是个指针,虽然和a一样的值,但指向的是整个数组内存块的首地址,当b进行+1运算时,会增加整个数组内存字节数大小
c是个数组,包含了10个int* 指针元素的数组
d是个指针,指向了一个包含10个int元素的数组
深入学习函数指针
函数原型
在函数原型中,特征标即参数列表有如下规则:
- arr[] 和 *arr 等价
- 可以省略标识符 const double ar[] 简化为 const double [], const double *arr 同理const double *
- 函数定义时不可以省略!
包含函数指针的数组
const double * (*pa[3])(const double *,int) = {f1,f2,f3};
语句书写思路:先写出返回类型和特征标 const double * ()(const double*, int) 再向()内填写一个包含指针的数组表达式*pa[3]
指向包含函数指针数组的指针
const double * (*(*pa)[3])(const double *, int) = &pa;
语句书写思路:先写出返回类型和特征标const double * ()(const double *, int)再向()内追加 一个 指向包含指针元素的数组的 指针表达式*(*pa)[3]