指针(进阶讲解之一)

之前我们介绍了一下指针的定义和简单应用,在此基础上,我们要做出一些进步~

I have a 函数,I have a 指针,啊~然后。。。盘ta!!!


首先,我们需要明确一件很重要的事情:

函数传参数,传指针,传引用到底有什么区别???

传参数

简单的参数传递,相当于将变量copy了一下。传到函数中参与运算
这就像复印文件,不管对复印件进行什么样的骚操,原件始终如一(除非你对原件进行毁灭性破坏【大雾)
可以说,传参数是对主程序变量的一个有效保护

#include<iostream>

using namespace std;

void doit(int n) {
	n=n*n;
}

int main() {
	int n=5;
	doit(n);
	cout<<n<<endl;
	system("pause");
	return 0;
}

/*
输出:5
*/

传指针

当一个函数的参数是指针时,指向的是传入参数的地址
所以函数调用和函数内部使用规则都需要严格遵守指针使用规范
然而这就导致在进行运算时比较繁琐(讲真就是代码比较丑/哭)

#include<iostream>

using namespace std;

void doit(int *p) {   //参数变成了int *指针类型
	*p=*p * *p;       //*p表示取用p指向的变量
}

int main() {
	int n=5;
	doit(&n);        //取地址传入
	cout<<n<<endl;
	system("pause");
	return 0;
}

/*
输出:25
*/

传引用

传引用是C++特有的操作,C中并没有
指针和引用最大的区别:指针开辟了一块内存空间,专门用一块内存存储变量的地址;而引用是个原有的内存空间起一个别名,别名和原名公用一个地址

#include<iostream>

using namespace std;

void doit(int &p) {   //传递引用(别名)
	p=p*p;            
}

int main() {
	int n=5;
	doit(n);         
	cout<<n<<endl;
	system("pause");
	return 0;
}

/*
输出:25
*/

在这里我简单解释一下引用(别名):

int a=5;
int &_a=a;
_a=_a*_a;
cout<<a<<endl;

/*
输出:25
*/

可以看到,在程序中我给 a 起了一个别名 _a (看清楚定义方式哦)
int &_a=a;
要格外注意的是,在定义时就要完成对于_a的初始化


动态分配和释放内存

动态分配——new

在C++程序中,所有内存需求都是在程序执行之前通过定义所需的变量来确定的。但是可能存在程序的内存需求只能在运行时确定的情况。在这些情况下,程序就需要动态分配内存。

在这里我就非常想@那些想要在程序里用变量(非常量)定义数组大小的宝贝们,你们的福音来了~

new 常用格式:
<指针变量>=new<数据类型>

几种使用形式:

  • 给单个对象申请分配内存
  • 给单个变量申请分配内存的同时初始化该变量
  • 同时给多个变量申请分配内存

一定要注意申请的方式哦!!!

int *p;        //定义指针
p=new int;     //申请指向int类型的指针
*p=9           //初始化
int *p;
p=new int(8);     //申请一个指向int类型的指针,变量初始化为8

又来插一句: 括号出现,基本上是三种情况:函数,类(模板),对象(构造函数初始换对象)
这里的括号可以视为是进行对象的初始化

int *p;
p=new int[5];    //申请一个长度为5的int类型数组
//-------------------------------------------
int *p;
int n;
cin>>n;
p=new int[n];    //用变量(非常量)定义数组大小

需要注意的是,用new分配内存时不一定能申请成功
毕竟针对每一个程序计算机分配的内存是有上限的,不可能满足在程序内部无止尽动态申请内存

在以下的实验中,我们不停申请大小为102410244字节的数组,计算机最多允许申请大概400个

#include<iostream>
#include<new>  //for "bad_alloc" exception
#include<cstdlib>

using namespace std;

int main()
{
	int *ptr,t=0;
	try {
		while(1) {
			ptr=new int[1024*1024];
			memset(ptr,0,sizeof(ptr));
			cout<<++t<<endl;
		}
	}
	catch (bad_alloc e)
	{
		cout<<"No sufficient memory to alloc!\n";
		exit(1);
	}
	system("pause");
	return 0;
}

释放内存——delete

当程序不再需要由new分配的内存空间时,我们可以使用delete释放这些空间

delete 常用格式:

  • delete<指针变量>
  • delete[]<指针变量>
int *p;
p=new int;
*p=9;
delete p;
int *p;
p=new int(8);
delete p;
int *p;
p=new int[5];
delete []p;
打起精神来注意啦:
  • 用 new 运算符申请分配内存的内存空间,必须用delete释放
  • 对于一个已分配内存的指针,只能用 delete 释放一次
  • delete 作用的指针对象必须是由 new 分配内存空间的首地址
  • 用 new 运算符为多个对象申请内存空间时,不能提供初始化
  • 如果指针为 0 ,则对此进行 delete 是合法的
    int *p=0; delete p;//okk
  • 在 delete 后,被删除指针变为悬空指针,指向曾经存放对象的内存,但是该对象已经不存在

SIZEOF

之前我尝试简单介绍了一下sizeof的用法,那么下面我就尝试着 “ 不简单的 ” 给大家再补充一点

sizeof 是用来计算占用的以字节为单位的内存空间

  • 对于常量和变量名,我们呢在使用时是否带括号可以选择:
    int number; sizeof(number); //siezof number
  • 而对于类型名则必须带括号:
    sizeof(char); sizeof(Gradebook);

那么sizeof与指针有什么关系呢?
看下面的程序:

#include<iostream>

using namespace std;

int getSize(double *s) { return sizeof(s);}   //int getSize(double s[])

int main()
{
	double a[20];

	cout<<"The number of bytes in a is "<<sizeof(a)<<endl;
	cout<<"The number of bytes returned by getSize is "<<getSize(a)<<endl;

    system("pause");
	return 0;
}

/*
输出:
The number of bytes in a is 160
The number of bytes returned by getSize is 4
*/

第一个得到的结果是 20(数组大小)* 8(double所占字节)=160

但是第二个sizeof计算的实际上是数组a地址的大小
经过不懈的实验,会发现无论什么类型的数组,得到的结果都是4
(别问,问就是不会)

The number of bytes returned by getSize is 4

指针的运算

指针支持算术,赋值,比较运算
而指针的运算一般与数组结合使用

int v[5]={0};
int *vptr=&v[0];   
//int *vptr=v;

在这里插入图片描述

最初vptr指向v[0]
我们在这里需要注意一下数组元素的地址,因为int占四个字节,而数组在内存中一定占据一块连续的内存,所以每一位地址在数值上相差4(记录方式采用十六进制)

指针原酸与数值算数运算存在很大区别:
数值运算:
int num=0x00AFFD90; num++; num==0x00AFFD91;
指针运算:
指针vptr指向元素v[0],值为00AFFD90
vptr++运算后,指向v[1],值为00AFFD94
指针运算(假设指向数据类型type的指针)时,+/-n表示前移/后移n个元素,其中n称为offset(偏移值)
从数值上看,指针的值是加/减了n*sizeof(type)

int v[5]={0};
int *vptr1=&v[1];
int *vptr2=&v[3];   
  • 自增&&加法赋值运算
    • vptr++ => v[1]
    • vptr+=3 => v[3]

在这里插入图片描述

  • 自减&&减法赋值运算
    • vptr-- => v[3]
    • vptr-=3 => v[0]

在这里插入图片描述
下面看这个厉害的操作:
在这里插入图片描述

int x=vptr2-vptr1;
cout<<x;

/*
输出:2
*/

两个指针相加得到的偏移量,这就启发我们get了一个计算数组大小的新思路

需要特别注意的是:两个指针之间进行加法操作是毫无意义的
  • 赋值运算
int num=1633837924;
int *pNum=&num;
char *pChar=(char *)pNum;
cout<<*pChar<<endl;
cout<<pChar<<endl;
cout<<*(++pChar)<<endl;   //注意前置自增和后置自增的区别
输出:
d
dcba
c

好啦,隔着屏幕看见你们mengbier的表情后,我来解释一下到底发生了什么:

  • 首先,不同类型的指针式之间式不能进行赋值运算的,所以我们强制转换,把int类型的指针转换成了char类型的指针
int num=1633837924;   
//int num=0x61626364
  • 注意我给出的num,在十六进制下(计算机中地址的记录方式统一使用十六进制)是 0x61626364
    我们知道int类型占用四个字节,每个字节是八位二进制,等价于两位十六进制(2^8-1=255)
    计算机内部存储时,实际上是把一个二进制数字每八位为一个单位(十六进制每两位为一个单位) 存入字节中,那么61626364这一串数字在计算机中就变成了这样:
    |61|62|63|64|
    转为十进制:
    |97|98|99|100|
    在强制转化成字符之后,对应的字符分别为:
    |a|b|c|d|
  • 在转化过程中,字符反序
    (为什么?别问,问就是不知道。实践出真知)
  • *ptr表示当前位的一个元素

通用指针

仅有相同类型的指针之间可以进行赋值运算,特例:通用指针void *

int num=0; int *ptr=&num; void *p=ptr;
任意类型指针均可以赋值给通用指针,反之不成立
通用指针仅用于保存地址值,不能进行解引用,不能进行算术运算
cout<<*p<<endl; //error C2100:illegal indirection
cout<<p+1<<endl; //error C2036:'void *':nukown size

  • 等价运算符,判断某指针是否为空
if (ptr==0)  //NULL
    cout<<"error"<<endl;
else ...
  • 关系运算符,一般用于数组
int a[5]={1,2,3,4,5};
int *p=a;
do {
    cout<<*(p++)<<' ';
}
while (p<=&a[4]);

用const修饰指针

一句话——就近原则:
constant pointer:指针常量,指针类型是常量

int * const p;

pointer to constant:常量指针,指向常量的指针

const int *p;
分类 non-constant data constant data
non-constant pointer int * p1 const int * p2
constant pointer int * const p3 const int * const p4
  • p1: Nonconstant Pointer to Nonconstant data

    被指向变量的值可以改变,指针的指向也可以改变

  • p2: Nonconstant Pointer to Constant data

    常量指针,被指向变量的值不可以改变,但是指针的指向可以改变

  • p3: Constant Pointer to Nonconstant data

    指针常量,声明时必须同时进行初始化,或作为形参通过实参初始化
    被指向变量的值可以改变,指针的指向不可以改变
    int num=100; int * const p=&num;

  • p4: Constant Pointer to Constant data

    指向常量的指针常量,常量指针+指针常量

#include<iostream>

using namespace std;

void printCharacters(const char *ptr) {
	for (;*ptr!='\0';ptr++) cout<<*ptr;
	//for (int i=0;*(ptr+i)!='\0';i++) cout<<*(ptr+i);
}

int main()
{
	//const char phrase[]="print characters of a string";
	const char *phrase="print characters of a string";
	cout<<"The string is:\n";
	printCharacters(phrase);
	system("pause");
	return 0;
}

在上面这个实验中,我们需要注意以下两种定义方式是不同的:

const char *phrase="print characters of a string";

声明一个指向常量字符串的指针,也就是说指针和字符串分别占据空间

const char phrase[]="print characters of a string";

定义一个常量字符串,占用空间小于上一种定义方式

如果函数的定义变成这样:

void printCharacters(const char * const ptr) {
	for (;*ptr!='\0';ptr++) cout<<*ptr;
}
//error C2166:I-value specifies const object

那么你就会很伤心的发现编译失败
因为这时候我们的形参是一个指向常量字符串的指针常量,即指针本身也是一个常量,对于指针我们是不能进行运算操作的

发布了941 篇原创文章 · 获赞 192 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/wu_tongtong/article/details/103739517