指针
1指针的定义和基本使用
注意
提醒:你必须先前面的所有视频按顺序学完,否则听不懂这 节谭。
如果直接跳到本次课,则很可能听不懂。
(1) 内存用于存储数据,最小单元是字节(8bit),每个单元都有一个编号(即地址: 0x00000000~0xFFFFFFFF)
(2) 变量有大小
(sizeof操作符, int大小是4),变量的大小指的是它在内存中占了 几个字节。
(3) 变量有地址,变量的地址
就是该变量在内存中的位置 。 用&号可以取得地址
(4) 变量赋值,就是向内存写入数据。读变量的值,就是从内存读取数据。
(5) 每个变量都有自己的内存地址
,绝不会有2个变量的内存地址相同。(一个萝卜 一个坑。。。)
(6) 程序每次运行时,变量的地址是不一样的,无法预测也不需要预测其地址。
查看变量的地址
地址是一个整数: 0x00000000 ~ 0xFFFFFFFF
int a = 0;
double b = 0;
printf(“%08X
\n”, &a);
printf(“%08X \n”, &b);
也可以
printf(“%p
\n”, &a); // p: pointer
如何表示地址?
地址是整数
,是否可以用 int ? (理论上可以。。。)
但是为了强调它是个内存地址,提出一些新的类型。。。
char* : 表示一个char型变量的地址
short* : 表示一个short型变量的地址
int* : 表示一个int型变量的地址
float* : 表示一个float型变量的地址
double*: 表示一个double型变量的地址
unsigned char*: 表示一个unsigned char型变量的地址
…
XXX* : 表示XXX型变量的地址
如何表示地址?
以下以int型变量为例:
int a = 10;
int* pa = &a;
定义一个变量pa,类型为int*,初始值为&a。
有两种叫法:
(1)
pa指向了一个int型变量
(2)
pa所在的内存是一个int型整数
我们称int*为指针类型
,pa为指针类型的变量(简称指针
)
关于指针
(1) 指针也是变量,是可以变的。
int a = 10;
int b = 11;
int* p = &a; // p: 指向a
p = &b; // p: 指向b
可以把它视为一种整型变量。
(注意:这里只说了支持赋值操作符=,而关于加减乘除等 其他操作符,后面再说。。。)
关于指针
(2) 不同类型的指针,不能互相赋值
int a = 10;
int* pa = &a; // pa: int型
double pb = pa; // 错!! 左侧double*, 右侧int*
(int*: 指向int型变量, double*:指向double型变量,肯 定不能互相赋值)
又如:
char a = 78;
float* p = &a; //错:左侧为float*,右侧为char*
关于指针
(3) 指针即地址,地址是整数,所以指针是一个整数类型
int a ;
int* p = &a;
printf(“addr: %08X \n”, p);
也可以使用 “%p”,效果和%X差不多
printf(“addr: %p\n”, p);
关于指针
(4) 星号的位置 以下几种均可以,没有区别,比较自由
int a; // int与连写
int * a; // int与中间有空白
int *a; // *与变量名连着
都可以,哪种都差不多。作者的习惯是第一种写法。
星号操作:按地址访问
已知一个int型变量,有两种方式来改变它的值。
(1)直接使用变量名赋值
int a = 0;
a = 0x11;
a = 0x12;
a = 0x1314;
(2) 使用指针(按地址访问,直接修改内存)
int a = 0;
int* p = &a; // p指向a
*p = 0x11;
*p = 0x1314;
星号操作:按地址访问
有一个指针变量p,则p用于访问p指向的变量(p指向的内 存)。 或者称作:*p用于访问p指向的内存(读、写)
int a = 0x1314;
int p = &a; // p指向a所在的内存
*p += 2;
int b = *p; // 取得p指向内存的值
int c = *p + 2;
星号操作:更多练习
是区分: 指针定义语句中的* 和 按地址访问的*
int a = 10;
int b = 11;
int* p ; // 定义一个指针, 注意int*作为一个整体
p = &a; // p指向a
printf(“ %d \n”, *p); // *p: 取得p处内存的值
p = &b; // p指向b
printf(“%d \n”, *p); // *p: 取得p处内存的值
星号操作:int不支持星号操作
虽然说指针是一种整数类型,但是它是特殊的,*号只能作 用于指针类型。
int addr = 0x12345678; // addr: 普通int型变量
*addr = 0; // 编译错误! 只有指针才能支持星号操作
星号操作: 其他指针类型
其他指针类型的用法也是完全一样的。。。
char a = 78;
char* p = &a;
p = 79;
double m = 1.1;
double pm = &m;
*pm = 1.2
…完全一样
小结
(1)提出新的类型 char* /short*/int*…用于表示某型 变量的地址。
int* p = &a; // p指向变量a,或p指向a的内存
(2)学习按地址访问:星号操作
*p = 1234;
int b = *p;
2指针与数组
回顾数组
长度为4、类型为int的数组:
int arr[4] = {1,2,3,4};
在内存中查看:直接输入数组名…
这说明:数组名就是一个内存地址, 用printf打印..
数组名
在C/C++中,数组名就是地址,就是数组在内存中的位置。 它表示第一个元素的地址(简称:数组的首地址)。
int arr[4] = {1,2,3,4};
int* p = arr; // arr本身的类型就是int*
相当于
int* p = &arr[0]; // 第一个元素的地址
指针加减法
指针加法:后移n个元素
指针减法:前移n个元素
比如:
int arr[4] = {1,2,3,4};
int* p = arr;
p += 1; // 后移一个元素
printf(“%d \n”, *p);
p -= 1; // 前移一个元素
printf(“%d \n”, *p);
指针加减法: 与int的加减法的区别
int arr[4] = {1,2,3,4};
int* p1 = &arr[0];
int* p2 = p1 + 1;
用printf打印一下,可以发现地址是加4 这很容易理解:对于int来说,元素大小是4字节,所以后移一 个元素、自然地址要加4
同理,对char
char arr[4] = {1,2,3,4};
char* p1 = &arr[0];
char* p2 = p1 + 1;
指针与数组之间的关系
(1)用p指向数组中arr的任意一个元素
例如,指向arr[3]
第一种方法:
p = arr + 3;
第二种方法:
p = &arr[3];
指针与数组之间的关系
(2) 给数组元素赋值
第一种方法:
arr[3] = 10;
第二种方法:
* (arr + 3) = 10;
* 或 int* p = arr + 3;
*p = 10;
指针与数组之间的关系
(3) 把p可以当成数组使用
int* p = &arr[1];
p[0] = 0xAA; // p[0]: 自p开始的第0号元素, 即arr[1]
p[1] = 0xBB; // p[1]: 即arr[2]
int a = 10;
int* p = &a;
p[0] = 11; // 长度为1的数组
指针与数组之间的关系
(4) 数组的遍例
第一种方法:
int arr[4];
for(int i=0; i<4; i++)
{
printf(“%d \n”, arr[i]);
}
第二种方法:
int arr[4];
for(int* p= arr; p<arr+4; p++) // 注: arr+4
{
printf(“%d \n”, *p);
}
指针与数组之间的关系
(5)越界访问
int arr[4] = {1,2,3,4};
arr[4] = 10; //运行时错误!
注:错误分为编译错误和运行错误。编译错误由编译检查, 属于语法上的错误。运行错误只有在运行时才出错,一般会 导致程序崩溃。
使用指针访问数组元素时,也不能越界。
int* p = arr;
* (p + 4) = 10; // 运行时错误!// 严重的隐惑
如何看待数组?
直接把数组成看一块内存就可以了。
数组名就是这块内存的地址。
然后通过指针来访问它就可以了。
小结
数组名:本身就是内存地址, 第一个元素的地址
数组与指针可以灵活的转换
3指针作为函数的参数
指针作为函数的参数
可以把指针作为函数的参数
void test(int* p) // 把一个内存地址传给一个函数
{
// test: 我能拿指针干什么?
}
int main()
{
int a = 0;
test(&a);
}
指针作为函数的参数
可以把指针作为函数的参数
void test(int* p) // 把一个内存地址传给一个函数
{
// 使用星号操作*p,来读写内存
printf(“before test: %d \n”, *p);
*p = 1;
printf(“now :%d \n”, *p);
}
使用指针作为参数,可以实现两种功能: (1)可以读取上一层函数中变量的值 *p (2)可以修改上一层函数中变量的值 *p (普通的参数无法实现)
指针作为函数的参数
例: 求两数的和
void sum (int* a, int* b, int* out)
{
int result = *a + *b; // 读取输入
*out = result; // out保存输出, 不使用返回值
}
int main()
{
int a=10, b=11;
int out = 0;
sum(&a, &b, &out);
return 0;
}
指针作为函数的参数
例: 交换两个变量的值
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
int main()
{
int a=10, b=11;
swap(&a, &b);
return 0;
}
传递数组作为参数
数组名,实质是一个指针类型,传递数组,就是传递指针。
int avg(int* p, int len)
{
}
把数组信息传给一个函数:
(1)首地址 : 一片连续内存地址
(2)长度 : 这块内存上存储的对象的个数
注:此后称内存里存储的元素为“对象”
传递数组作为参数
数组名,实质是一个指针类型,传递数组,就是传递指针。
int avg(int* p, int len)
{
int sum = 0;
for(int i=0; i<len; i+)
{
sum += p[i];
}
return sum/len;
}
传递数组作为参数
在调用的使用,也是自由的
int main()
{
int arr[] = {1,2,3,4};
int ret ;
ret = avg(arr, 4); //从arr[0]到arr[3]
ret = avg(arr, 3); // arr[0] … arr[2]
ret = avg(arr + 1, 3); // arr[1] … arr[3]
}
显然,对于avg来说,不关心你是不是叫“数组”;在它眼里, 它只接收到了一个内存地址而已。
传递数组作为参数
注意事项
(1)以下两种方法完全等价,没有任何区别。
int avg(int* p
, int len) 和 int avg(int p[]
, int len);
(2) 传递数组时,总是要另外传递长度信息 不能只把首地址传给函数,这样是不够的。 int avg(int* p, int len); // 总是要传长度信息
传指针有什么作用?
把一个指针传给函数,有什么作用呢?
(1)返回多个值 : return只能返回一个值,如果一个函数要 返回多个值,必须使用指针参数(输出参数)
(2)效率问题:传值与传地址
传指针有什么作用:返回多个值
写一个函数,求出一个数组的最小值和最大值
void max_min(int* p, int len, int* max, int* min)
{
}
注:
把用于输入的参数称为输入参数,如p, len
把用于输出的参数称为输出参数,如max, min
传指针有什么作用:效率问题
此问题通常称为:传值和传地址 / 传值或传引用reference
void test1(int a)
{
}
void test2(int* p)
{
}
int main()
{
int n = 0;
test1(n); // 传值:把n的值传递给函数
test2(&n); // 传地址:把n的地址传递给函数 return 0;
}
小结
(1)学会把变量的地址传给函数
(2)学会把数组信息传递给函数,传数组时必须要长度信息
(3)用指针作为输出参数,返回多个值
(4)明白“传值和传地址”的区别,及效率上的差异
4const指针的用法
const指针
在普通指针类型前面,加上const修饰
例如:
const int* p;
const char* p;
const double* p;
const指针:区别
加不加const, 有什么区别?
(1) 不加const
int a = 10;
int* p = &a;
*p = 11; // 可写 int b = p; // 可读
(2) 加上const修饰
int a = 10;
const int p = &a;
*p = 11; // 错误! 不可写
int b = *p; // 可读
所以,const的作用是封禁(限制)星号操作里的写内存功能 称为:只读的ReadOnly,这块内存只能读不能写
const指针:用途
const用于限定函数的参数
int test(const int* p, int len)
{
}
用于显示地指定:该函数是输入参数,在函数里只是读取这 个内存、而不会修改这个内存的值。
当你不需要修改内存时,在指针前面加const修饰,避免一 不小心的错误发生。
const指针: 注意事项
const用于限定函数的参数
int avg (const int* p, int len)
{
for(int i=0; i<len; i++)
{
printf(“%d \n” , *p); // 可以读
p = p + 1; // 没问题
}
}
const只是封禁的是星号操作,不允许写内存 但对于普通的指针加减是没关系的
一个不常用的语法
以下知识点仅在考试中可能被考到,实际工程中不会用到。
int a;
int b;
int* const p = &a; // 这是另一种语法
p = &b; // 语法错误:p不能修改
大家的重点是熟悉 const int*这种语法
小结
(1)const指针:表示该内存是只读的
(2)常用于修饰函数的参数,当被const修饰,表示该参数仅 用于输入。 int avg( const int* arr, int len);
5如何安全地使用指针
最不靠谱的写法
先看一种最不靠谱的写法。。。
int * p;
printf(“ %d \n”, *p);
指针不可乱用
安全地使用指针,需要有对指针足够清楚的认识。
在使用指针之前,一定要弄清楚两个问题: (1)这个指针指向了哪儿? (2)这个指针指向的那个地方是否有效?(是否能够访问)
指针指向了哪儿?
就目前来说,指针只允许指向两个地方:
(1)指向了变量、数组
(2)指向0
第一种情况:
int n;
int* p1 = &n; // p1指向的内存:一个变量
int arr[4];
int* p2 = arr; // p2指向的内存:一个数组
第二种情况:
int* p = 0; // 空指针
指针指向了哪儿?
也就是说,这个指针要么指向了一个变量/数组,要么指向0。 不允许有第三种情况。
int * p; // 这个指针指向哪了?一个莫名其妙的地方。。
printf(“ %d \n”, *p); // 立即崩溃
当一个指针未赋值时,其值为随机值,此时指向了一个随机 的内存地址。称为“野指针”wild pointer
空指针
值为0的指针,称为空指针。
int* p = 0;
当指针为空时,不能使用星号操作。
int* p = 0;
printf(“%d \n”, *p);
但空指针是程序员可以接受的一种情况,只需加一个if判断 就能解决!
if§
printf(“%d \n”, *p);
空指针:应用
某些参数可以省,当不想传入时就传一个空指针
void max_min(const int* arr, int len, int* pmax, int* pmin)
{
…
if(pmax) *pmax = _max;
if(pmin) *pin = _min;
}
调用时
int _max;
max_min(arr,4, &_max, 0); // 表示只需要得到最大值
安全使用指针: (1) 杜绝野指针
一个好习惯:初始化为空指针。。。
int * p = 0;
考虑:为什么传递一个空指针是允许的?而传一个野指针是 不允许的?
(空指针也会崩溃)
int * p = 0;
printf(“ %d \n”, *p); // 空指针也会崩溃
安全使用指针: (1) 杜绝野指针
void test(int* p)
{
// 在使用指针前判断
if( p != 0)
{
printf(“%d \n”, *p);
}
}
传递空指针:函数内部是有办法处理,判断一下就行。 传递野指针:函数内无法判断出来你是个野指针!!
安全使用指针: (2) 严防数组越界
当指针指向数组时,要注意不要越界访问
int arr[4];
int* p = arr;
p += 4;
*p = 12; // 已经越界!但不容易察觉!
安全使用指针: (3) 变量是否已经失效?
如果指向的目标的生命期已经终结(失效),则该指 针也失效。 int main()
{
int* p = 0;
if(1)
{
int a = 10; // a生效
p = &a; // p指向a
} // a失效
*p = 11; // p指向了一个无效的位置
return 0;
}
在VC不一定能发生运行错误,但不代表可以这样做!
(在19,20章引入类的概念之后,可以很容易说明这个问题的严重性)
安全使用指针: (3) 变量是否已经失效?
所以,指向全局变量的指针,安全性相对较高。因为它的生 命期是永恒的,这一块内存总是有效。
小结
- 野指针的概念
- 空指针的概念及应用
- 安全使用指针的注意事项
a,野指针-不能存在 b,空指针-用之前if判断 c,变量指针-变量的作用域 d,数组指针-数组越界