指针与动态内存分配
指针变量
定义一个变量,程序执行后数据回家再到内存的某个地方,计算机根据其地址来找到该变量
地址:内存按照字节编号,0、1、2、3、4、…
变量的地址:变量占用的首个存储单元的地址
指针变量的使用要点
- 指针变量在声明时要初始化,否则就成为了野指针
- 数据类型必须匹配使用,不可以在不同类型指针变量之间赋值
- 指针变量存储的是某一个数据类型的地址,不可以将一个数据类型直接赋给指针变量
void 指针及强制类型转换
注意:已被淘汰,使用安全性很低了解即可
1 | int a=7,*pa=&a; |
任何类型的指针都可以赋值给 void
类型的指针,并且 void
指针不能够查看自身存储的地址,但是经过强制转换为符合的类型是可以查看所储存的地址和地址指向的内容的
指针与一维数组
- 通过下标访问数组的元素
1 | int a[10]; |
- 通过数组名访问数组的元素
有一种看法是数组其实就是指针
1 | int a[10]; |
数组指针——指向数组的指针
多维数组指针声明方法:数据类型 (*指针变量名)[常量表达式]
因为
[]
的优先级要高于*
,所以要用括号改变优先级结合
1 | int a[2][3]={...}; |
指针数组——存储指针的数组
多维指针数组声明方法:数据类型 变量名[常量表达式]
1 | int a,b,c,d; |
通过指针访问二维数组的三种方式
1 |
|
二级指针
二级指针指的是指向指针的指针,由此指针还可以继续向多级延伸
1 | int a=5; |
二级指针经常和指针数组配合使用,因为指针数组内部元素就是一个指针,数组名本身又是一个指针,所以二维指针可以访问指针数组
1 | char **p; |
动态内存分配
程序内部结构
代码块:程序代码,由 exe
文件中相应内容载入后形成,只读
静态数据区:全局变量、静态数据成员和静态局部变量。全局变量、静态数据成员在程序启动后占用存储空间,静态局部变量在第一次执行时创建,如果编程时没有对这些数据初始化,系统将自动初始化为 0
栈区:局部变量、函数参数与返回值,由操作系统自动维护,编程时未初始化将赋予随机值
堆区:由程序员动态请求的数据区,运行时确定,由程序员负责申请和释放,C++ 支持 new
和 delete
运算符,用于在堆中动态创建对象和释放对象,使用 new
申请内存可能会因为内存不足而失败
动态申请内存方法
1 | int *p; |
exit(o) 、exit(1) 和 return 0 的区别,前者是退出程序,后者是退出函数
1 | int *p=new int(5);//分配空间并初始化为5 |
避免内存泄漏
1 | int *pa=new int(5),*pb=new int(10); |
1 | void fun(){ |
- 不能对动态请求的内存连续使用
delete
释放两次 - 注意
delete
的本质,释放的是处在栈区的指针变量所指向的堆区内的地址,指针变量本身则在程序结束后由程序自动释放其在栈区占用的空间
释放指针变量pa指向的堆地址的内容后,pa还是指向着那个堆内的地址,只不过内容变了
常指针和指向常量的指针
注意:去别在于 const
和谁结合
- 常指针:
const
在*
后面,说明该指针变量本身是常量,所以指针存储的地址值不能够修改,但是地址指向的内容还是可以修改的
1 | char str1[]{"abcd"}; |
- 指向常量的指针:
const
在*
前面面,说明指针变量本身存储的地址值可以修改,但是指向的地址的内容是常量不可以修改
1 | char str1[]{"abcd"}; |
- 指向常量的常指针:顾明思议就是上述两种指针的结合,无论是指针本身存储的地址还是地址指向内容都不可以修改
1 | char str1[]{"abcd"}; |
constexpr
关键字
和 const
类似,只不过这个是用于指明常量表达式的 constexpression 的缩写
- 常量表达式:编译是即获得计算结果的表达式
1 | const int max=20;//常量表达式 |
constexpr
声明的变量必须是用常量表达式初始化
1 | constexpr int mf=20;//编译通过 |
编译阶段确定初始值是常量才可以声明为 constexpr
,编译器会在编译阶段进行检查
注意:C++11 之前最后一个是不通过编译的因为变量无法确定初始值,但是 C++11 之后若是将函数声明为 constexpr 则可以通过编译,相当于告诉编译器将函数看做是某个常量
1 | constexpr int square(int x){ |
nullptr
空指针常量
旧版本中使用 NULL
和 0 表示空指针,但是这样存在一定的歧义
1 | void f(int); |
指针及参数传递
函数的值传递
函数的参数时局部变量在栈中,由于调用函数是在不同的作用域所以会发生新的栈的空间申请和变量的复制
1 | int sum(int a,int b){ |
注意:其实函数的返还机理是函数将 a+b
赋给一个在栈中申请的新的临时变量,然后临时变量返还内容后被释放
函数指针传递
方法:将变量地址传入函数,从而达到间接修改 main()
内的变量
1 | void sum(int *a,int b){ |
检查自己是否真的了解函数指针传递的机理:
1 | void xhg(int *a,int *b){ |
下面的程序 x 和 y 内容没有发生改变而是交换了两个变量存储地址的内容,这是为什么呢?
1 | void xgh(int *a,int *b){ |
因为 temp 也是指针变量,所以起初存储的是 b 地址,当 b 存储的地址赋值为 a 的地址的时候,temp 的地址也跟着发生了改变,所以最终没有成功交换地址的内容,只是 xhg 内的函数的指针变量存储的地址发生了改变
指向函数的指针
理解:应该是可以借助这个指针可以访问多个函数。和数组名一样,函数名就是指向函数的指针
1 | int add(int a,int b){ |
引用的概念
引用是变量或对象别名,建立引用是必须确定引用的对象,对引用的操作实际上就是对被引用者的操作(即引用变量本身不申请额外的空间,引用变量和被建立引用的对象公用的是同一个空间地址)
为什么有指针了还要有引用?实际上引用就是用指针封装实现的,发明出来的原因是减少程序员对指针的恐惧
1 | int i=1; |
以后对 ri 的操作实际上就是对 i 的操作,可以认为 ri 和 i 在内存中占用的是相同的空间单元
函数的引用传递
引用只是别名,并不位其分配存储单元
1 | void sum(int &a,int b){ |
函数返还指针
1 | char * elem(char *s,int n){ |
函数找到修改的地址完后返还一个地址但是会在函数结束的时候销毁,只能
main()
内另开一个指针记录下来地址再进行修改
函数返还引用
1 | char & elem(char *s,int n){ |
因为返还的类型是主函数 str 引用地址不会销毁,所以直接修改函数返还的就可以
返还引用和指针的陷阱
注意:要格外注意变量的生存周期和所处的作用域
- 不能向主函数返还指向其他函数局部变量的指针
- 不能向主函数返还指向其他函数局部变量的引用
因为上述两者都会导致在函数结束时变量被释放,再在主函数内访问就是未知的了
函数递归调用
1 | //求阶乘 |
函数的参数值
- 带缺省值的函数,是一种声明行为,作为不全参数的缺省值
- 可以在不同的作用域内声明函数的不同缺省值
注意:函数声明内前面放没有指定默认值的参数,从某个位置开始及以后的参数都要含有默认值
1 | int sum(int a,int b=2,int c=3) |
内联函数
- 函数调用便于模块化设计,使程序结构清晰
- 函数调用占用一定的系统资源:保护现场、参数入栈
- 作用:将函数的编译后的模块直接嵌入调用处,避免了函数调用的开销
- 适用于代码量少的函数,且不能为调用函数
尽量减少使用 define
宏
因为宏是在编译之前将源代码进行替换所以没用类型检查
函数重载
- 多个函数使用相同的函数名但是函数的参数类型或者参数的个数不同
- 不可以仅仅函数的返还类型不同而进行函数重载
- 通过重载可以实现对不同类型参数进行调用的统一版本
函数重载注意问题
尽量避免二义性
1 | int fun(int a,int b=0); |
可变参数的实现
1 |
|
STL基础组件要览
string字符串处理
- C++ 通过
string
封装了字符串的处理 - 不用担心字符数组的维数以及最后末尾的
\0
string
对象隐含了其自身的状态信息,封装了大量的操作并且更加安全可靠string
支持标准库的众多算法
string 定义和初始化
1 |
|
string 基础操作
1 | string s,s1; |
在 cctype
头文件中存在一系列对于字符的操作函数
截取 string
string s1 = s2.substr(2, 5);
第一个参数内指定截取开始的位置,缺省值为 0 表示从头开始截取,第二个参数指定截取的字符数量(长度),缺省即截取余下的所有字符,若是指定的长度比实际余下的要多就截取余下的所有字符,函数返还新的字符串 stirng
字符串查找
1 | find()//在一个字符串中查找指定的字符或子串,若找到,返回首次匹配的位置,若没找到,返回string::npos |
删除字符
s2.erase(2,'a');
第一个参数表示开始删除字符的位置,缺省值即为0,第二个参数表示要删除的字符数,缺省为删除剩下的所有字符,若第二个参数值比实际剩下的字符数多,只删除剩下的所有字符