第八章 指针\第八章 指针

时间:2022-11-22 07:35:40
/*
//1 什么是地址
#include <iostream>
using namespace std;
int main()
{
    int i=0;
	cout<<&i<<endl;
	return 0;
}
*/

/*
// 2 用指针保存地址
#include <iostream>
using namespace std;
int main()
{
	int i=1;
	int *p;
	p = &i;
	cout<<"&i:"<<&i<<endl;
    cout<<"p:"<<p<<endl;

	//输入信息
	cout<<"i的值是:"<<i<<endl;
	cout<<"p的值是:"<<*(p)<<endl;

	return 0;
}
*/

/*
 我们知道指就是用来保存内存地址的变量,因此我们定义一个指针后一定要用它来保存一个内存地址,假如我们不那么做
 那么该指针就是一个失控指针,它可以指向任何地址,并且对该地址的数值进行修改或者删除,后果非常可怕的
解决办法是将该指针初始化为0
*/
/*
// 3 空指针
#include <iostream>
using namespace std;
int main()
{
	int *p = 0;
	cout<<"p:"<<p<<endl;
    return 0;
}
*/

/*
由于不同类型的变量在内存中所占用的字节不同,而指针又是用来保存内存地址的变量,因此指针只能存储与他类型相同的变量的地址
//邮于指针类型的不同决定了指针运算方式的不同,所以我们不能将一种类型的指针赋给另一种类型的指针
//这就是指值与变量类型的关系,指针的类型必须与它所指向的变量的类弄相本,假如不相配,那么就会报错!
*/
// 4 指针与类型
/*
#include <iostream>
using namespace std;
int main()
{
    double a = 3.14;
	int b = 6;
	int *p1 = &b;
	double *p2 = &a;

	cout<<"p1:"<<*(p1)<<endl;
	cout<<"p2:"<<*(p2)<<endl;
	a++; //?????????没弄懂这里的a++后咱p1的地址上还是6呢
	//在我理解来看,都是在一个地址上,如果我a++了,那么该地址上的值就应该也有变化才对
	//操自己弄乱了。理解是正确的
	cout<<"p2:"<<*(p2)<<endl;
	

	/*cout<<"p1:"<<p1<<endl;
	cout<<"p2:"<<p2<<endl;
	p1++;
	p2++;
	cout<<endl;
	cout<<"a:"<<a<<endl;
	cout<<"b:"<<b<<endl;
	cout<<"p1:"<<*(p1)<<endl;
	cout<<"p2:"<<*(p2)<<endl;
	cout<<endl;

	cout<<"p1:"<<p1<<endl;
	cout<<"p2:"<<p2<<endl;*/

	/* s = 5;
	int *ss = &s;
	cout<<"s:"<<s<<endl;
	cout<<"ss:"<<ss<<endl;
	cout<<"*(ss):"<<*(ss)<<endl;
	cout<<endl;
	s++;


	cout<<"s:"<<s<<endl;
	cout<<"ss:"<<ss<<endl;
	cout<<"*(ss):"<<*(ss)<<endl;
	

}*/

/*
我们在得到某人的地址后,就可以根据该地址找到此人的家,并见到此人,同理,指针的运算符*也可以为我们做到这些。
运算符*被称为间接引用运算符,当使用星号*时,就读取它后面变量中所保存的地址处的值。
//指针变量p前面的间接运算符*的含义是:"存储在此地址处的值"。*p的意思就是读取该地址处的值,由于p保存的是a的地址,因此执行的结果是输出a的值:1.
//5 用指针访问值
#include <iostream>
using namespace std;
int main()
{
     int a= 1;
	 int *p;
	 p = &a;
	 cout<<"*p:"<<*p<<endl; //1
	 cout<<"p:"<<p<<endl;   //地址
	 cout<<"&a"<<&a<<endl;  //地址
	 cout<<"a:"<<a<<endl;   //1
	 return 0;
}
*/

//////////////////////////////////
/////6 容易混淆的概念
/*
指针最容易混淆的概念是:指针地址,指针保存的地址,和该地地址的值,也就是说指针它自身的地址,指针保存的地址和指针保存的地址处的值是最容易令初学者混淆的三个概念

//通过本节学习我们了解到指针的地址与指针中保存的某个变量的地址是不一样的,每个指针都有一个地址
//而在该地址中保存的则是另一个变量的地址 
*/
/*
#include <iostream>
using namespace std;
int main()
{
    int i;
	int *p = 0;
    //int a=5;
	//p=&a;
	cout<<"i的地址为:"<<&i<<endl;
	cout<<"p的值为:"<<p<<endl;
	cout<<"p的地址为:"<<&p<<endl;
	//cout<<"p的所保存的值为:"<<*p<<endl; //如果为空指针,就不能访问所保存的值了
	i = 3;
	cout<<"i的地址为:"<<&i<<endl;
	p = &i;
	cout<<"p的值为:"<<p<<endl;
	cout<<"p的内存地址为:"<<&p<<endl;

}
*/

//第七节  指针对数值的操作
/*
你通过某人的地址找到了某人的家,那么你完全可以对他家的各种东西进行操作,比如说:打碎他家的窗户,把他本人打一顿,只要你敢于或者愿意这么做
同理,计算机通过间接运算符*号访问并且读取到该项地址的数据,那么它也可以修改这些数据
*/
/*
#include <iostream>
using namespace std;
int main()
{
    typedef unsigned short int ut; //声明一个类型为无符号的短整型
	ut i = 5;  //声明一个ut类型的变量i
	ut *p = 0; //声明一个ut类型的指针p,为空指针
	p = &i;
	cout<<"i="<<i<<endl;   //5
	cout<<"*p="<<*p<<endl; //5
	cout<<"用指针来修改存放在i中的数据"<<endl;
	*p = 99;
	cout<<"i="<<i<<endl;    //99
	cout<<"*p="<<*p<<endl; //99
	cout<<"用i来修改存放在i中的数据"<<endl;
	i=88;
	cout<<"i="<<i<<endl;    //88
	cout<<"*p="<<*p<<endl; //88
	return 0;

}
*/

//第八节 更换指针保存
/*
//我们可以将一个变量的值赋给另一个变量
//那么我们可不可以对指针再进行赋值,比如说我们能不能更换指针保存的地址
*/

/*
#include <iostream>
using namespace std;
int main()
{
	int i=0;
	int j=1;
	int *p = &i;
	cout<<"i的值:"<<i<<endl;
	cout<<"&i地址:"<<&i<<endl;
	cout<<"j的值:"<<j<<endl;
	cout<<"&j地址:"<<&j<<endl;
	cout<<"p的值:"<<p<<endl;
	cout<<"*p所保存的值:"<<*p<<endl;


	p = &j;
	cout<<"更换地址以后:"<<endl;
	cout<<"i的值:"<<i<<endl;
	cout<<"&i地址:"<<&i<<endl;
	cout<<"j的值:"<<j<<endl;
	cout<<"&j地址:"<<&j<<endl;
	cout<<"p的值:"<<p<<endl;
	cout<<"*p所保存的值:"<<*p<<endl;

	return 0;
}
*/

//9.1 为什么使用指针
/*
很多初学习都会问,既然通过变量名就可以访问数据,为什么还要使用繁琐而又容易出错的指针呢?
这是因为在操作大型数据和类时,由于指针可以通过内存地址直接访问数据,从而避免在程序中复制大量的代码,因此指针的效率真最高
一般来说,指针会有三大用途
1 处理堆中存放的大型数据
2 快速访问类的成员数据和函数
3 以别名的方式向函数传递参数
在讨论这些问题之前,我们先来理解几个


 
 栈和堆
 一般来说,程序就是与数据打交道,在执行某一功能的时候该功能所需要的数据加载在内存中,然后执行完毕的时候释放掉该内存
 数据在内存中的存放共分为以下几个形式:
 1 栈区(stack)-由编译器自动分配并且释放,该区域一般存放函数的参数值,局部变量的值等
 2 堆区(heap)-一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收
 3 寄存器区  -用来保存栈顶指针和指令指针
 4 全局区(静态区)(static) 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
 程序结束后由系统释放
 5 文字常量区- 常量字符串就放在这里的,程序结束后由系统释放
 6 程序代码区 存放函数体的二进制代码

函数参数和局部变量存放在栈中,当函数运行结束并且返回时,所有的局部变量和参数就都被系统自动清除掉了。
为的是释放掉它他所占用的内存的空间,全局变量可以解决这个问题,但是全局变量永远不会被释放,
而且收于全局变量被所有的类成员和函数所共享,所以它的值很容易被修改,使用堆可以一举解决这两个问题

接下来我们着重讨论下堆和栈之间的区别,通过两者之间的比较,我们可以明白为什么堆可以解决以上两个问题

注:好多时候我们都把堆和栈放在一起说,比如堆栈,其实他们是不同的,至于为什么把他们混在一起说,这是历史问题,这里就不深究了


1 内存申请方工上的不同
  栈:
    由系统自动分配,例如我们在函数中声明一个局部变量int a;那么系统就会自动在栈中为变量a开辟空间
  堆:
    需要程序员自已申请,因些也需要指明变量的大小

2 系统响应的不同
  栈:
    只要栈的余空间大于所申请空间,系统将为程序提供内存,否则将提示overflow,也就是栈溢
  堆:
    系统收到程序申请空间的要求后,会遍历一个操作系统用于记录内存空亲地址的链表,当找到一个空间大于所申请空间的堆结点后
就会将该结点从记录内存空闲地址的链表中删除,并将该结点的内存分配给程序,然后在这块内存区域的首地址处记录分配的大小,这样我们在使用delete来释放内存的进修,delete
才能正确地识别并删除该内存区域的所有变量,另外,我们申请的内存空间与堆结点上的内存空间不一定相等,这时系统就会自动将堆结点上多出来的那一部份内存空间回收到空间链表中

3 空间大小的不同
   栈: 在window下,栈是一块连续的内存的区域,它的大小是2M,也有的说是1M,总之该数值是一个编译时就确定的常数,
是系统预先根据栈顶的地址和栈的最大容量定义好的,假如你的数据申请的内存空间超过栈的空间,那么就会提示overflow,因此,别指望栈能存储比较大的数据
   堆: 堆是不连续的内存区域,名块区域由链表将它闪串联起来,关于链表的知识将在后面的章节中讲解,这里只需要知道链表将各个不连续的内存区域链接起来
这些串联起来的内存空间叫做堆,它的上限是收系统中有效的虚拟内存来定的,因些获得的空间比较大,而且攻得空间的方式也比较灵活

4 执行效率的不同
  栈
    栈由系统自动分配,因些速度较快,但是程序员不能对其进行操作
  堆
    堆是由程序员分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来很方便


  5 执行函数的不同
    栈
	在函数调用时,第一个进栈的是被配用函数下一行的内存地址,其次是函数的参数,假如参数多于一个,那么次序是从右往左,最后才是函数的局部变量
	由于栈的先进后出原则,函数结束时正好与其相反,首先是局部变量先出栈,然后是参数,次序是从左到右,这时所有的变量都已出栈,指针自然指到第一个进栈的那行内存地址,也就是被调用函数下一行内存地址,程序根据该项地址跳转到被调用函数的下一行自动执行
	到于栈内数据为什么要先进后了, 这个原理可以用叠盘子来做个比喻,你将一个一个盘子放在另一个盘子之上依次将它他叠高,取走的时候必然是从最上面的盘子开始,你不可能直接抽出最下面的盘子,因为*也会知道那样做会摔碎所有的盘子
    我们看到栈的生长是向着内存地址减小的方向增长的,即:假如再有数据进入栈,那么就会放置琶101这个位置,数据不断地增长,那么内存地址就会不断减少。
*/
/*
#include <iostream>
using namespace std;
int main()
{
	//int *p; //创建一个指针
	//p = new int; //在堆中创建一个大小为int型的地址,然后保存到p中

	double *p = new double; //在堆中创建一个大小为double类型的地址
	*p = 1.62;
	cout<<"*p:"<<*p<<endl;
	//注意:
	//由于你的计算机的内存是有限的,因此可能会出现没有足够内存而无法满足new的请求,在这种情况下,new会返回0
	//该值被赋给指针后,那么该指针就是一个空指针,空指针不会指向有效数据,new除了返回空值之外,还会引发异常
	//在后面的异常处理中将会讲解



    return 0;
}
*/

// 9.5 用指针删除堆中空间
/*
#include <iostream>
using namespace std;
int main()
{
	int *p = new int;
	*p = 3600;
	cout<<"*p="<<*p<<endl; //3600
	delete p; //删除创立在堆中的空间
	cout<<"*p="<<*p<<endl;

	p = 0; //然后把p指针初始化为一个空指针
	p = new int; //在堆中创建一个int型大小的空间给p
	*p = 8;
	cout<<"*p:"<<*p<<endl; //8
	delete p;
	cout<<"*p="<<*p<<endl;



    return 0;
}
*/

// 10 内存泄露
	//假如没全个指针就对其重新赋值,如:
	//第一行定义了一个指针p并使其指向一块内存空间,第二行又将一块新的内存空间的地址赋给了p,
	//这样第一行所开辟的那块内存间就无法再使用了,因为指向它的指针现在已经指向了第二块空间
	//指针变量p只能保存一个地址,对它重新赋值则表示以前的地址被盖,假如该地址的内存空间没有
	//被释放,那么你将无法再次通地p访问它,因为此时的指针变量p记录的是第二块内存地址
	//假如暂地不相删除第一块内存空间,那么你可以这么做:
	//int *p1 = new int;
	//int *p2 = new int;
	//分别用两个指针来指向两块内存空间,这样每块空间都有一个指针来指向,也就不会成找不到某块空间的内存泄露现象.
	//注意:你在使用new以后,假如不再使用该块内存空间,那么一定要用delete来释放它

/*
#include <iostream>
using namespace std;
int main()
{
	//int *p1 = new int;
	//delete p1; //需要先将p1删除,然后在进行地址分配
	//p1 = new int;//现在这种是错误的,因为

	//注意: 你在使用new以后,假如不再使用该块内存空间,那么一定要用deleter来释放它

    return 0;
}
*/
//11 在堆中创建对像
//我们既然可以在堆中保存变量,那么也就可以保存对像,我们可以将对像保存堆中,然后通过指针来访问它
/*
#include <iostream>
using namespace std;
class Human
{
public:
	Human(){ cout<<"构造函数执行....."<<endl;}
	~Human(){ cout<<"析构函数执行....."<<endl;}
private:
	int i;
};

int main()
{
	Human *h = new Human(); //在堆中创建一个对像
	delete h;  //删除在堆中的对像



    return 0;
}*/

// 13 访问堆中的数据成员
/*
#include <iostream>
using namespace std;
class Human
{
public:
	Human(){ cout<<"构造函数执行完成"<<endl; i=99;}
	~Human(){ cout<<"析构函数执行完成"<<endl; }
	void get()const{ cout<<"i:"<<i<<endl; }
private:
	int i=0;

};

int main()
{
	Human jack;
	jack.get();


	Human *man = new Human();
	man->get();


    return 0;
}
*/


// 14 在构造函数中开辟内存空间
/*
#include <iostream>
using  namespace std;

class Human
{
public:
	Human(){ cout<<"构造函数执行开始.."<<endl;  p = new int(999);}
	~Human(){ cout<<"析构函数执行开始.."<<endl; delete p;}
	int get(){ return *p;}
private:
	int i;
	int *p; //在类中声明一个指针
};

int main()
{
	Human *p = new Human();
	cout<<p->get()<<endl;
	delete p;
    return 0;
}
//该	例仅仅是为了说明构造函数中也可以开辟堆中空间,在实际程序中,一个堆中创建的对像通过成员指针再创建新空间用来保存数据并没有什么意义,因为在堆中创建对像时已经为它的所有数据成提供了保存的空间
*/


// 15 对像在栈与堆中的不同
/*
#include <iostream>
using namespace std;
class Human
{
public:
	Human(){ cout<<"构造函数执行开始.."<<endl;  p = new int(999);}
	~Human(){ cout<<"析构函数执行开始.."<<endl; delete p;}
	int get(){ return *p;}
private:
	int i;
	int *p; //在类中声明一个指针
};

int main()
{
	Human *p = new Human(); //这里是不会执行析构函数,这里需要手工来删除存个子在内存中的p信息
	delete p;

	cout<<endl;
	Human s;
	//在堆中的函数程序是不会自动删除在内存中的函数,需要程序员自已来清理
	//而栈中的函数,系统会自动清理空间



    return 0;
}
*/

// 16 this指针
/*
#include <iostream>
using namespace std;
class A
{
public:
	int get()const{ return i; }
	void set(int x){ this->i=x; cout<<"this变量保存的内存地址为:"<<this<<endl;}
private:
	int i;
};
int main()
{
	A a;
	a.set(99);
	cout<<"对像a的内存地址是:"<<&a<<endl;
	cout<<a.get()<<endl;

	A b;
	b.set(44);
	cout<<"对像b的内存地址是:"<<&b<<endl;
	cout<<b.get()<<endl;
	//由于this指针保存了对像的地址,因此你可以通过该指针直接读取某个对像的数据,它的作用将会在后面的重载运算符得到演示,
	//现在我们只需要知道this变量保存的是对像的地址,那么this指针就是指向对像的指针,另外this指针的创建与删除由编译器来完成,你不操心

    return 0;
}
*/

// 17 指针的常见错误
/*
#include <iostream>
using namespace std;
int main()
{
	int *p = new int;
	*p = 3;
	cout<<"将3赋值给p的地址后,指针读取的值:"<<*p<<endl; //3
	delete p;
	
	//p = 0;
	//虽然使用空指针是非法的,容易地使程序崩溃,但是我们宁愿程序崩溃,也不愿意高度起来困难
	//cout<<"删除空间后,指针p读取的值:"<<*p<<endl;

	long *p1 = new long;
	cout<<"创建空间后,指针p中保存的地址:"<<p<<endl;
	*p1 = 999999; //这个应该是数值已经超出了指针类型的大小
	cout<<"指向新空间的指针p1保存的地址是:"<<p1<<endl;

	*p = 33;
	cout<<"将23赋给p的地址后,指针p读取的值:"<<*p<<endl;
	cout<<"将23赋给p的地址后,指针p1读取的值:"<<*p1<<endl;

	cout<<endl;
	cout<<"创建空间后,指针p中保存的地址:"<<p<<endl;
	cout<<"指向新空间的指针p1保存的地址是:"<<p1<<endl;



    return 0;
}
*/


//第十八节 指针运算(指针的加减运算)
/*
#include <iostream>
using namespace std;
int main()
{
	int *p = new int;
    cout<<"批针p保存的空间地址为:"<<p<<endl;
    p ++; //将指针变量p的内存地址自加,由于p指向是int型变量,因此执行加1的操作会将原来的内存地址增加4个字节

	cout<<"自加后,指针p保存的空间地址为:"<<p<<endl;
	p --;
	cout<<"自减后,指针p保存的空间地址为:"<<p<<endl;
	p = p -2;
	cout<<"减2后,指针保存的空间地址为:"<<p<<endl;

	//p = p -20;
	//这里就有点不明白了,本来指针一个int型就只有4个字符,现在把p-20,这里的减只是说内存地址的变化,而并不是指内存大小的变化吗?
	//那么p++;为什么是把p的所站有的空间加了4个字节呢
	*p = 44;
	cout<<"减去2后,赋值44给p指针,现在所保存的值是:"<<*p<<endl;


	return 0;
}*/

// 第十八节 指针运算  (二)指针的赋值运算
/*
#include <iostream>
using namespace std;
int main()
{
	//指针也可以进行赋值运算
	//比如将一个指针的变量地址赋给另一个指针变量地址

	int *p =  new int;
	*p = 88;
	cout<<"*p的值为:"<<*p<<endl;
	cout<<"p所保存的值为:"<<p<<endl;

	int *s =  new int;
	cout<<"s的值为:"<<s<<endl;
	s = p;
	cout<<"将p赋值给s后, s所保存的值为:"<<s<<endl;
	cout<<"将p赋值给s后,*s的值为:"<<*s<<endl;




    return 0;
}
*/


// 第十八节 指针运算 (三)指针的相减运算
/*
#include <iostream>
using namespace std;
int main()
{
	int *p = new int;
	cout<<"p:"<<p<<endl;
	int *p1 = new int;
	cout<<"p1"<<p1<<endl;
	*p = p - p1; //将两块内存的地址差赋给*p
	//这里计算出来的是在内存地址中的p 与p1两块地址之间的地址差
	cout<<"两块内存的地址差:"<<endl;
	cout<<*p<<endl;
    return 0;
}*/


//第十八节 指针运算 (4) 指针的比校运算
//两个指针这前也可以进行比较运算
/*
#include <iostream>
using namespace std;
int main()
{
	int *p = new int;
	cout<<"p:"<<p<<endl;
	int *p1 = new int;
	cout<<"p1:"<<p1<<endl;
	if(p > p1){//这里只是比较两块内存地地址小大
	   cout<<"p大于p1的内存地址"<<endl;
	}else{
	   cout<<"p小于p1的内存地址"<<endl;
	}
    return 0;
}
*/

// 第十九节 const与指针 (一)常量指针
//我们可以将指针定义为常量指针,这样该指针不可改变
/*
#include <iostream>
using namespace std;
int main()
{
	int a = 3;
	int *const p = &a;
	cout<<"a:"<<a<<endl;
	a = 4;
	cout<<"a:"<<a<<endl;
	//p++; //常量p的指针是不能修改的
    return 0;
}*/
/*
#include <iostream>
using namespace std;
class A
{
public:
	int get()const{ return i;}
	void set(int x){ i=x;}
private:
	int i;
};

int main()
{
	A *p = new A;
	cout<<"p:"<<p<<endl;
	p = p+1; //这里的指针地址是可以修改
	cout<<"p:"<<p<<endl; 

	A *const s = new A;
	//s = s+1; //常量指针是不能进行修改的
	s->set(11);
	cout<<s->get()<<endl;




    return 0;
}
*/

// 第十九节 const与指针 (二)指向常量的指针
/*
#include <iostream>
using namespace std;

class A
{
public:
	int get()const{ return i;}
	void set(int x){ i=x;}
private:
	int i;
};

int main()
{
	A *p = new A;
	cout<<"p:"<<p<<endl;
	p = p+1; //这里的指针地址是可以修改
	cout<<"p:"<<p<<endl; 

	//A *const s = new A; //这样定是一个常量的指针
	const A *s = new A;   //这样s就变成了指向常量的指针,该指针指向的目标是不可修改的,但是该指针可以被修改



	//s = s+1; //常量指针是不能进行修改的
	//s->set(11); //指向常量的指针只是不能修改指向常量的内容,指针本身是可以修改的
	s++;
	s--;
	cout<<s->get()<<endl;





    return 0;
}*/

// 第十九节 const与指针 (二)指向常量的常指针
#include <iostream>
using namespace std;

class A
{
public:
	int get()const{ return i;}
	void set(int x){ i=x;}
private:
	int i;
};

int main()
{
	A *p = new A;
	cout<<"p:"<<p<<endl;
	p = p+1; //这里的指针地址是可以修改
	cout<<"p:"<<p<<endl; 

	//A *const s = new A; //这样定是一个常量的指针
	//const A *s = new A;   //这样s就变成了指向常量的指针,该指针指向的目标是不可修改的,但是该指针可以被修改

	//const A const *s = new A; //指向常量的常指针,这样写错误的
	const A* const s = new A; //这样写才是正确的





	//s = s+1; //常量指针是不能进行修改的
	//s->set(11); //指向常量的指针只是不能修改指向常量的内容,指针本身是可以修改的
	//s++;
	//s--;
	cout<<s->get()<<endl;

    return 0;
}