音视频开发之旅(20) 指针、内存模型、引用
目录
- 指针
- 内存模型和四区使用
- 引用
- 资料
- 收获
一、指针
1.1 什么是指针?
指针本质上是地址,用于指向具体的内容
表达方式:数据类型 *名称
eg:
int a=1;
int *p = &a; //指针p指向a的地址
count << a<< endl;
count << *p <<endl;
其中指针运算和地址运算互为逆运算。
即&用于取地址;*用于指针指向的具体内容
1.2 指针常量、常量指针
指针常量:指针类型的常量, int *const p = &a;
本质上是一个常量, 指针指向的内容值可以修改,但是指针的指向(地址)不可以修改。
常量指针:指向常量的指针, const修饰符 const int *p1= &a;
本质上是一个指针,指针指向的内容值不可以修改,但指针的指向(地址)可以修改
下面用示例说明:
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
int a = 1;
int b = 2;
//指针常量:本质上是一个常量,指向的内容可以修改,但是指针的指向(地址)不可以改变
int * const p =&a;
*p =10;
// p = &b; //错误 指针常量只能修改指向内容的值,但是不能修改地址指向
cout << *p<<endl;
//常量指针:本质上是一个指针,指针指向的内容不可以改变, 但指针的指向(地址)可以变化
const int * p2 = &a;
p2 = &b;
// *p2 = 2; //错误 常量指针 只能修改地址指向,但不能修改指向内容的值
cout << *p2 <<endl;
return 0;
}
1.3 空指针与野指针以及void*类型的指针
空指针 ,一般用于指针的无指向时的初始化。指向地址为0的地址。
int *p = NULL;
或者
int *p =0;
运行时对空指针取值会报错,因为没有访问权限。示例如下
int *p;
cout<< p<<endl;
cout<<*p<<endl; //Exception has occurred. EXC_BAD_ACCESS (code=1, address=0x0)
野指针 :指向了 内存被释放的内存 或者 指向了没有访问权限的内存地址的指针
野指针的原因以及如何避免
- 指针没有被初始化,有可能不会自动置为NULL指针而是是随机的 —》保险起见,指针初始化是赋值为具体的值或者NULL;
- malloc分配内存的指针,被free或者delete后,没有置为NULL,继续使用
- 指针的操作超过了变量的作用域
(void*)类型的指针
可以指任何类型的指针但不能直接用void指针进行操作,需要先转为对应类型。相当于java中的Object
1.4 数组指针 与指针数组
数组指针:是一个指针,这个指针指向的是一个指向数组,后者说是指针的首地址。
char tmp[2] = {'a','b'};
char *p = tmp;
//char *p =&tmp[0];
cout<<*p<<endl;
cout<< *(p+1)<<endl;
指针数组:本质上是一个数组,数组中的每一个变量都是指针类型。
char tmp[2][3] = {
{'a','b','c'},
{'d','e','f'},
};
char (*p)[3] = tmp;
cout<< *(*(p+1)+2)<<endl;
二维数组与指针数组
二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。而指针数组的内存分布上可能是不连续的
1.5 指针函数与函数指针
指针函数:本质上是一个函数,只是他的返回值为指针类型。
函数指针:本质上是一个指针,指向函数的指针
指针函数,即指针类型的函数,函数的返回类型是指针
格式如下
类型说明符 * 函数名(参数)
eg:
int *fun(int a,int b)
示例如下:
#include<stdio.h>
#include<iostream>
using namespace std;
int *addAddr(int a,int b){
static int c= a+b; //注意:不要将非静态局部地址用作函数的放回值,容易出现野指针。
return &c;
}
int main()
{
int a = 1;
int b = 2;
int *p = addAddr(1,2);
cout<<p<<endl;
cout<<*p<<endl;
return 0;
}
注意:不要将非静态局部地址用作函数的放回值,容易出现野指针。
指向函数的指针:指向的是程序代码存储区,指向函数代码的首地址
形式:存储类型 数据类型 (*函数指针名称)();
eg
int (*f) (int x);//声明一个函数指针
f = func; //将func函数的首地址复制给指针f
示例如下:
#include<stdio.h>
#include<iostream>
using namespace std;
int add(int a,int b){
int c= a+b;
return c;
}
int sub(int a,int b){
int c= a-b;
return c;
}
//函数指针 fun
int (*fun)(int a,int b);
int main()
{
int a = 1;
int b = 2;
fun = add;
cout<<fun(a,b)<<endl;
fun = sub;
cout<<fun(a,b)<<endl;
return 0;
}
函数指针作为函数的参数:地址传递(引用传递)区别于值传递
典型用法:实现函数的回调
1.6 对象指针
类名 *对象
对象指针名->成员名
eg: Object *ptr;
ptr->getx()相当于(*ptr).getx();
二、内存分类模型、动态内存分配
2.1 内存分区模型
程序运行之前分配的内存区域:全局区和代码区
全局区:全局变量、静态变量、常量(字符串常量、const修饰的全局变量):程序结束后由操作系统自动释放
代码区:存放函数体的二进制机器指令,代码区只读的且是共享的
程序运行之后分配的内存区域:栈区、堆区
栈区:编译器自动分配,存放一些局部变量、形参,会自动回收。有编译器管理开辟和释放,不要返回局部变量的地址。
堆区:动态内存分配,程序员主动申请和释放,通过 malloc free new delete
int a = new int(1);
delete a;
int a[] = new int[10];
delete a[];
动态分配的多维数组
int (*fp)[3] = new int[2][3];
delete fp[];
2.3 智能指针
三、 引用
引用的本质是给变量起别名,指向的是同一块内存,修改别名指向的值,原名指向的值也一样修改。
语法: 数据类型 &别名 = 原名
引用必须要初始化,引用初始化后就不能再改为别的变量的引用(别名)。
示例代码如下:
int main()
{
int a = 1;
int &ref = a;
cout << ref <<endl;
ref = 100;
cout <<a<<endl;
}
**引用作为函数参数**
//值传递、地址传递、引用传递 形参数修饰实参数
void swap(int &a,int &b){
int temp = a;
a=b;
b=temp;
}
int main()
{
int a = 1;
int b = 2;
swap(a,b);
cout << a <<endl;
cout << b <<endl;
}
引用作为函数的返回值
不要返回局部变量的引用
函数的调用可以作为左值
引用的本质:在cpp内部的实现是指针常量(指针的指向不可以修改,指针指向的内容值可以修改)
int a=1;
int& ref =a
—>
int* const ref = &a;
常量引用
用来修改时形参,防止误操作
资料
[什么是野指针和内存泄漏?如何避免野指针] : https://blog.csdn.net/qq_35212671/article/details/51920851
[C++基础教程]: http://c.biancheng.net/cplus/
收获
- 学习回顾指针相关的概念和使用
- 熟悉内存模型
- 动态分配内存以及智能指针的使用
- 引用的使用
感谢你的阅读
下一篇我们继续学习实践cpp知识 继承、多态、模版,欢迎关注公众号”音视频开发之旅”,一起学习成长。
欢迎交流