笔记:C++学习之旅---指针
为什么要使用指针
因为在操作大型数据和类时,由于指针可以通过内存地址直接访问数据,从而避免在程序中赋值大量的代码,因此指针的效率最高,一般来说,指针会有三大用途:
1:处理堆中存放的大型数据。
2:快速访问类的成员数据和函数。
3:以别名的方式向函数传递参数。
我们先来看内存中的几大区: 内存到底分几个区?
一:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由os回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码。
函数参数和局部变量存放在战中,当函数运行结束并且返回时,所有的局部变量和参数就都被系统自动清除掉了,为的是释放掉他们所占用的内存空间。全局变量可以结局这个问题,但是全部变量永远不会被释放,而且全局变量被所有类的成员和函数所共享,所以它的值很容易被修改,使用对可以一一解决这个问题.
堆和栈的区别
1. 内存申请方式上的不同
(一)栈
油系统自动分配,例如我们在函数中声明一个局部变量int a;那么系统就会自动在战中为变量a开辟空间.
(二)堆
需要程序员自己申请,因此也需要指明变量的大小。
2.系统响应的不同
(一)栈
只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将提示overflow,也就是栈溢出。
(二)堆
系统收到程序申请空间的要求后,会遍历一个操作系统用于记录内存空闲地址的链表,当找到一个空间大于所申请空间的堆节点后,就会将该节点从记录内存空闲地址的链表中删除。并将该节点的内存分配给程序,然后将这块内存区域的首地址处记录分配的大小,这样我们在使用delete来释放内存的时候,delete才能正确地识别并删除内存区域的所有变量。另外,我们所申请的内存空间与堆节点上的内存空间不一定相等,这时系统将会自动将对接点上多出来的那一部分内存空间回收到空闲链表中。
3.空间大小的不同
(一)栈
在windows下,栈是一块连续的内存区域,它的大小是2M,也有的说是1M,总之该数值是一个编译时就确定的常熟。是由系统预先根据栈顶的地址和栈的最大容量定义好的。假如你的数据申请的内存空间超过栈的空间,那么就会显示overflow。因此,别指望栈能存储比较大的数据。
(二)堆
堆是不连续的内存区域,各块区域由链表将他们串联起来,这些串联起来的内存空间叫做堆,它的上线是由系统中有效的虚拟内存来定的。因此获得的空间比较大,而且获得空间的方式也比较灵活。
4.执行效率的不同
(一)栈
栈由系统自动分配,因此速度较快,但是程序员不能对其进行操作。
(二)堆
堆是由程序员分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来很方便。
5.执行函数时的不同
(一)栈
在函数调用时,第一个进栈的是被调用函数下一行的内存地址,其次是函数的参数,假如参数多于一个,那么次序是从右往左。最后才是函数的局部变量。先进后出,不容易产生内存碎片。
(二)堆
堆是一大堆不连续的内存区域,在系统中由链表将它们串接起来,因此在使用的时候必须由程序员来安排。它的机制是很复杂的,有时候为了分配一块合适的内存,程序员需要按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有满足条件的空间,那么就要向系统发出申请增加一部分内存空间,这样才有机会分到足够大小的内存,然后将计算后的数值返回。显然,堆的运行效率要比栈低得多,而且也容易产生碎片。但是好处就是堆可以存储相当大的数据,并且一些细节也可以由程序员来安排。
用指针创建堆
在C++中使用关键字new创建一个堆并分配内存,在new后面跟一个要分配的对象类型,编译器根据这个类型来分配内存。
如:int *p;
p = new int;
第一行第一行定义了一个指向整型的指针变量p,第二行用new在堆中创建一个int类型的内存区域,然后将该区域的内存地址付给了指针变量p,这样p所指向的就是这块新创建的内存区域。
同样可以定义为:int *p = new int;
由于使用new创建的内存空间不会被系统自动释放,因此假如你不去释放它,那么该区域的内存将始终不能为其他数据所使用,而指向该内存的指针是个局部变量,当定义该指针的函数结束并返回时,指针也就消失了,那么我们就再也找不到该内存区域了。(这样就叫做内存泄漏).这种糟糕的情况将一值持续到结束该区域的内存才能恢复使用,因此如果你不需要一块内存空间,那么就必须对之乡它的指针使用关键字delete。
如:int *p = new int;
delete p;
p = 0; (这将释放指针所指向的内存,而不会释放该指针,一定要记得设置为空指针,否则再次使用将会导致程序出错)
如果已经释放,当再次释放的时候程序将奔溃。
指针常见错误
删除一个指针后一定要将该指针设置为空指针,这是因为删除该指针只会释放它所指向的内存空间,不会删除指针,因此这个指针还存在,并且它仍然会指向原来的内存空间,因此当再次使用的时候,那么将会导致程序出错。
#include
<iostream>
using
namespace
std;
int
main()
{
int
*p =
new
int
;
*p = 3;
cout<<
"将3付给p的地址后,指针p读取的值:\t"
<<*p<<endl;
delete
p;
//删除指针以后没有将其置0,将导致后面程序再次对该指针使用,而p和p1是同一块内存地址,所以对p赋值23也意味着重新对p1赋值了
//p =0;//当将p置0为空指针后,再次使用该指针的时候,程序将出现奔溃,所以每次开辟空间的时候必须将该指针赋为空,宁愿报错,也不愿出现后面程序的错误导致很难调试。
cout<<
"删除空间后,指针p读取的值:\t\t"
<<*p<<endl;
long
*p1 =
new
long
;
//当删除p的内存以后没有将p置0,而当新开辟一块空间时,编译器将释放掉的空间赋给了p1,那么p1也指向了释放掉的空间;
cout<<
"创建新空间后,指针p中保存的地址:\t"
<<p<<endl;
//p和p1地址相同
*p1 = 999999;
cout<<
"指向新空间的指针p1保存的地址:\t\t"
<<p1<<endl;
*p = 23;
//意味着重新给p1赋值了;
cout<<
"将23付给p的地址后,指针p读取的值:\t"
<<*p<<endl;
// 所以打印的结果p = 23
cout<<
"将23付给p的地址后,指针p1读取的值:\t"
<<*p1<<endl;
//所以打印的p1 = 23;
delete
p1;
return
0;
}
在堆中创建对象
假如我们要删除在队中创建的对象,我们可以直接删除指向该对象的指针,这样会自动调用析构函数来销毁该对象同时释放内存。(调用delete手动释放)
#include
<iostream>
using
namespace
std;
class
Human
{
public
:
Human()
{
cout<<
"构造函数正在运行...\n"
;
i = 999;
}
~Human()
{
cout<<
"析构函数正在运行...\n"
;
}
private
:
int
i;
};
int
main()
{
Human
*p =
new
Human
;
//在堆中创建一个对象;
delete
p;
p = 0 //删除堆中对象,需要程序员手动销毁该内存;
return
0;
}
访问堆中变量和成员函数(->)
#include
<iostream>
using
namespace
std;
class
Human
{
public
:
Human()
{
cout<<
"构造函数正在运行...\n"
;
i = 999;
}
~Human()
{
cout<<
"析构函数正在运行...\n"
;
}
int
const
get()
{
return
i;
}
private
:
int
i;
};
int
main()
{
Human
*p =
new
Human
;
cout<<
"i的值:"
<<p->get()<<endl;
//p->get();使用->访问成员i;
delete
p;
return
0;
}
this 指针
this指针保存了对象的地址,因此你可以通过该指针直接读取某个对象的数据。
默认情况下this指针省略不写,this变量记录每个单独的对象的内存地址,而this指针则指向每个单独的对象。因此不同的对象输出的this变量的内存地址也不同。
#include
<iostream>
using
namespace
std;
class
A
{
public
:
void
set(
int
x
)
{
i =
x
;
//this 指针一般不写,可以写成this->i = x;
cout<<
"this 变量的保存的内存地址:\t"
<<
this
<<endl;
}
int
const
get()
{
return
i;
}
private
:
int
i;
};
int
main()
{
A
a;
a.set(10);
cout<<
"对象a的地址:\t"
<<&a<<endl;
//输出的this指针地址和对象a的地址相同,不同的对象this指针地址不同
cout<<a.get()<<endl;
A
b;
b.set(20);
cout<<
"对象b的地址:\t"
<<&b<<endl;
//输出的this指针地址和对象b的地址相同
cout<<b.get()<<endl;
return
0;
}
常量指针与指向常量的指针
常量指针自身不可以改变,但是它所指向的目标却可以改变,无论这个目标是变量还是对象。
/*常量指针*/
#include
<iostream>
using
namespace
std;
class
A
{
public
:
void
set(
int
x
)
{
i =
x
;
}
int
const
get()
{
return
i;
}
private
:
int
i;
};
int
main()
{
/*
int a = 3;
int *const p = &a;//指针本身的值不可以改变,但是它指向的目标是可以改变的。
cout<<"a的值:"<<a<<endl;
a = 4;
cout<<"a的值:"<<a<<endl;
//p++;不能做自加运算,p为常量指针,但是它所指向的值可以改变;
*/
A
*p =
new
A
;
cout<<
"p的地址:"
<<p<<endl;
p = p+1;
cout<<
"p的地址:"
<<p<<endl;
A
*
const
p1 =
new
A
;
//如果将其改为const A *p1 = new A;那么就变成了指向常量的指针,那么下一行代码p1->ste(11),将会报错,指针本身可以修改,可以执行p++操作,但是指向的目标常量不可以被修改;
//p1++;//左值是一个常量,意思是p1是常量指针,无法改变;
p1->set(11);
cout<<p1->get()<<endl;
return
0;
}
const
const 对象一旦创建以其值就不能被再改变,所以const对象必须被初始化,一如既往,初始值可以为任意复杂的表达式:
const int i = get_size();//正确,运行时初始化;
const int j = 42;//正确,编译时初始化;
const int k;//错误,k是一个未经初始化的常量;