c++指针和引用--lesson7


前言

指针是c/c++的重要概念,必须掌握!要搞清楚指针就需要搞清楚地址,地址范围很广:指针(它的范围很广,可以是普通变量的地址,可以是数组元素的地址(包括数组数组和字符数组),可以是函数的入口地址)、数组名(数组的第一个元素的地址,不能进行++指向下一个元素,类似于是一个const常量,或者是一个指针常量)、函数名(函数的入口地址),这才是使用指针的原因,太灵活了!
指针是地址,指针变量是存储地址的变量,①可以是变量的地址(数组元素是其中的特列),②也可以是函数的的地址,分别称为指向变量的指针变量,指向函的指针变量;③多维数组中某一行整体的地址;多维数组中整体的地址;⑤指针变量的地址(指针的指针)
下面将对①和②分别做说明,③和④参考8.多维数组和指针,⑤参考7.指针数组

在这里插入图片描述


一、指针

1.普通变量的指针是什么

1)程序经过编译之后已经将变量名转换为变量的地址,对变量值得存取都是通过地址进行的 2)直接存取和间接存取 3)指针:一个变量的地址称为该变量的指针,所以变量的指针就是变量的地址; 指针变量:一个存储指针(地址)的变量 ***4)两个重要的运算符:&--取地址运算符 和 * 指针运算符(指向的作用)***

在这里插入图片描述

2.指向普通变量的指针变量

1)定义:基类型 * 指针变量名
a)指针变量的基类型就是该指针指向的变量的类型;
b)定义指针变量的时候必须指定基类型,如果想指针引用一个变量,只知道地址(如0x080001000)是不行的,因为系统不知道是从这个地址取几个数据,所以一个变量的指针必须包含两个含义:变量的地址以及变量的类型。**指针变量是基本数据类型派生出来的数据类型,它不能离开基本类型而存在。**指针的类型就是(基类型 *),或者叫指向基类型的指针。
c)不能只用一个整数赋值给指针变量:int *pt = 200;可能你的想法是200是一个地址值,但是编译器不知道,会将200作为整数常量,因此会报错,所以赋值给指针变量必须是明确的编译器认为的地址,如&取地址符、数组名或者其他(这个地方需要再确认)
d)指针变量是在函数开始就定义的,此时已经确认了指针的类型,所以不能再修改,赋值给他的数据必须是相同基类型的变量取地址符,否则编译器会报错,编译器会认为你一开始就不知你要操作的数是什么类型,后面的操作就更不知道要乱搞什么,所以编译都不让你过!
e)指针变量一定要初始化或者赋合适的地址值,不然编译过了,但是使用的时候会报错。
2)直接存取和间接存取

3.指向普通变量的指针变量的使用场合

1)指针作为函数参数:函数的参数可以是整型、浮点型、字符型、数组、数组元素(整型、浮点型、字符型),还可以是指针,它的作用是将一个变量的地址传给被调用函数的形参。

// lesson_07.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include<iostream>
using namespace std;

int main(int argc, char* argv[])
{
	//函数原型声明,这样写更能体现函数的形参是一个指向整型的指针,
	//更建议大家这么写,形参的类型一目了然,类似于void swap(int [], int []),一看就知道形参是int型的数组
	void swap(int *, int *); 
	//定义性声明两个int变量
	int a, b;                
	//定义性声明两个指向int的指针变量,并初始化,注意指针变量一定要有确定的值再使用,
	//否则无确定值操作对系统是很危险的,要么定义的时候初始化,要么在操作前赋值
	int *pt1 = &a, *pt2 = &b;

	cout << "please input two numbers:" << endl;
	cin >> a >> b;
	
	swap(pt1, pt2);

	cout << "max =" << *pt2 <<", min = " << *pt1 <<endl;

	return 0;
}


void swap(int *p1, int *p2)
{
	int temp;

	if(*p1 > *p2)
	{
		temp = *p1;
		*p1 = *p2;
		*p2 = temp;
	}
}

2)指针和数组
通过前言,我们知道,数组名(如a[10])也是地址,只能指向第一个数组元素,类似于const常量,如果想进行a++;不行的,但是,int *b = a + 10;或者是int c = *(a + 5);类似于常量参与运算,是可以的,但是指针更灵活,关于数组与指针,讲以下3点:

int main(int argc, char* argv[])
{
	int a[] = {1,2,3,4};
	int *pt1 = a;//将数组首地址赋值给指针变量(地址变量)ptr, a是const常量
	
	for(int i = 0; i < 4; i++)
	{
//		cout << *pt1 <<endl;//地址指向的内容
		cout << *(a + i) << endl;
//		pt1++; //指针变量自加
		pt1 = a + i;
	}

	return 0;
}

a)程序中如有一个int a[5]的数组,引用其中a[3]这个元素,是怎么做的呢????实际上,编译器在编译的时候,是将这样处理的,首先找数组首地址a,之后a + 3找到a[3]的地址,最后使用指向运算符*(a + 3 * (int型数据的长度 2))找到a[3]这个数据,编译器将数组名作为一个指针常量;

//下标法
int main(int argc, char* argv[])
{
	int a[] = {1,2,3,4};
	
	for(int i = 0; i < 4; i++)
	{
		cout << a[i] << endl;
	}

	return 0;
}

//数组名指针常量法
int main(int argc, char* argv[])
{
	int a[] = {1,2,3,4};
	
	for(int i = 0; i < 4; i++)
	{
		cout << *(a + i) << endl;
	}

	return 0;

}

//将数组名赋值给一个指针变量
int main(int argc, char* argv[])
{
	int a[] = {1,2,3,4};
	int *pt1 = a;//将数组首地址赋值给指针变量(地址变量)ptr, a是const常量
	
	for(; pt1 < a + 4; pt1++)
	{
		cout << *pt1 <<endl;//地址指向的内容
	}

	return 0;
}

//三种方法中,最后一种效率最高,只有++和*两种操作符,速度最快,前两种都需要
// *(a + i * d(指的是数据类型长度))这一系列的操作,操作符较多,效率低

b)注意指针变量p指向的范围,如前面2.d)所说的,开始定义的时候说好的指向某种类型,但是没有说指针操作的范围,这是很危险的,需要程序员自身控制!!!!!!!!

c)通过指针变量来接受数组的首地址

int main(int argc, char* argv[])
{
	void swap(int *);
	
	int a[] = {1,2,3,4};
	swap(a);
	
	return 0;
}

void swap(int *p)
{
	cout << *(p + 1) << endl;
}

3)指针和字符串
通过前面的课程,我们知道,字符字符串的操作可以通过以下方式:
a)C-string:字符数组,这个地方要注意,char a[ ] = "I love China!"的执行过程,双引号做了三个工作,首先申请了空间(在常量区),存放了字符串,在字符串尾加上了“\0”,将内容复制到了a[ ]所定义的空间中,所以有两个地方是 "I love China!\0“,这个和字面常量还不一样,如int b = 5;字面常量不申请了空间(在常量区),直接将5内容填充到b中。
b)C++:字符串对象
c)指针法:char *ptr = "I love China!"的执行过程,双引号做了三个工作,首先申请了空间(在常量区),存放了字符串,在字符串尾加上了“\0”,返回地址,这里所返回的地址就赋值给了char *类型的指针变量ptr。

//将字符串str1复制到字符串str2中
int main(int argc, char* argv[])
{
	//声明str1字符数组,首先申请了空间(在常量区),存放了字符串,在字符串尾加上了“\0”,
	//将内容复制到了str1[]所定义的空间中,所以有两个地方是 "I love China!\0“
	//将数组名(第一个元素的地址)赋值给指针变量
	char str1[] = "I love china!",str2[20], *p1 = str1, *p2 =str2;

	//比较指针变量指向的内容是否是字符床结束符'\0'
	for(;*p1 != '\0'; p1++, p2++)
	{
		*p2 = *p1;
	}

	//最后在str2最后加上'\0'
	*p2 = '\0';
	
	cout << "str1 = " << str1 << endl;
	cout << "str2 = " << str2 << endl;
	
	return 0;
}

4.函数的指针是什么

一个函数在编译的时候被分配一个入口地址,这个地址就是函数的指针(或者叫函数的入口地址)。

5.指向函数的指针的指针变量

1)定义:函数类型 ( 函数指针变量名)(函数参数表)*
2)与普通变量的指针变量依托于基类型一样,函数指针变量名依托于函数的类型;
3)指针变量的类型:和普通变量的指针变量一样,指针是地址,没有类型,只有指向的类型,所以准确的说法是,指向函数类型的指针变量。

//指向函数的指针变量
int main(int argc, char* argv[])
{
	//函数原型声明
	void putout(int);
	
	//普通变量定义并初始化
	int a = 5;
	//函数指针定义不能直接初始化void (*po)(int a) = pout;这是不对的
    void (*po)(int a);

    //将函数名(函数的入口地址)赋值给指向函数的指针变量
    po = putout;

    //通过指针调用函数
    po(a);
	
	return 0;
}

void putout(int a)
{
	cout << a << endl;
}

6.指向函数的指针变量的使用场合

1)函数指针作为函数形参接收函数的入口地址,这样调用不同的函数;

//指向函数的指针变量做为形参
int main(int argc, char* argv[])
{
	//函数原型声明,有两个参数,一个是int型,一个是指向函数的函数指针
	//注意这里如果这样声明void putout(int a, void (*p)(int));则在上面个的基础上
	//定义了两个局部变量a,p,但是两者的作用于也仅仅在这里
	void putout(int , void (*)(int));
	void putout1(int);
	void putout2(int);
	
	//普通变量定义并初始化
	int a = 5;

	putout(a, putout1);
	putout(a, putout2);
	
	return 0;
}

//函数定义,有两个局部变量,一个是int型a,一个是指向函数的函数指针p
void putout(int a, void (*p)(int))
{
	p(a);
}

void putout1(int a)
{
	cout << a << endl;
}

void putout2(int a)
{
	cout << (float)a / 10 << endl;
}

2)函数的返回值(函数类型)可以是整形、实型、字符型,当然也可以是指针类型(即返回地址):定义:函数类型 * 函数名(函数参数表),注意与指向函数的指向变量的区别。

7.指针数组和指向指针的指针

1)指针数组:类型名 * 数组名[数组长度 ]
a)注意和**类型名( * 指针名)[数组长度 ]**的区别

//指针数组
int main(int argc, char* argv[])
{
	void sort(char *[], int);
	//定义一个指针数组,初始化的时候"hello"等都会有地址,并将地址放在p1数组中
	char *p1[3] = {"hello","c/c++","learn you!"};

	sort(p1, 3);

	cout << *p1 << endl;
	cout << p1[1] << endl;
	cout << p1[2] << endl;

	return 0;
}

void sort(char *p[], int n)
{
	char *temp;
	for(int i = 0; i < 3; i++)
	{
		for(int j = i + 1; j < 3; j++)
		{
			if(strcmp(p[i], p[j]) > 0)
			{
				//将指针数组中的值换一下,这样指向就不一样了,
				//但是字符串在字符串在内存中的地址和内容不变
				temp = p[i];
				p[i] = p[j];
				p[j] = temp;
			}
		}
	}
}

2)指向指针的指针
在这里插入图片描述

8.多维数组和指针

在这里插入图片描述

9.指针其他注意事项

①指针定义的时候与const结合
a)const 类型名 * 指针变量名 或者 类型名 const * 指针变量名:限制通过指针变量修改指向对象的值,对象的值能不能该,看对象自己是怎么定义的,我只能管到指针不要乱搞;
b)类型名 * const 指针变量名:指针变量的值是确定的,不能修改指向对象;
a)const 类型名 * const指针变量名:以上两着的结合

//const指针指针
int main(int argc, char* argv[])
{
	int a = 10, b = 20;

	const int *p1 = &a;
	//int const*p1 = &a;//和上面是一样的
//	*p1 = 50;//试图通过p1修改a的内容,不允许
	a = 50; //这是允许的
	//如果想a也不能改变,就定义成常变量const int a = 10;

	int  * const p2 = &a;
//	p2 = &b;//试图通过p2内容,不允许

	const int *const p3 = &a;
//	*p3 = 50;//试图通过p3修改a的内容,不允许
//	p3 = &b;//试图通过p3内容,不允许

	return 0;
}

②void指针类型,注意与NULL的区别
a)定义:void *指针变量,表示指针变量的指向的基类型不确定,或者称为基类型为void,所以不能是用此指针变量进行操作,因为编译器也不知这么操作,他仅仅只能存储一个地址值;
b)这种类型的指针是一个过渡态,不能使用的,必须转换成具体的指向类型才能使用,编译器要求的,列如malloc函数开辟了一段空间,函数返回的就是空间的首地址,至于你要在里面存上面类型的数据,他是不知道的,所以返回的就是void *型的指针;
c)可以将非void的指针赋值给void的指针变量,但是void的指针赋值给非void的指针变量时必须强制转化。

//void *和非void *
int main(int argc, char* argv[])
{
	float c = 3.14;//truncation from 'const double' to 'float'
	//float c = 3.14f;//不加f默认时double
	float *p1 = &c;
	void *p2;

//	p1 = p2;//error C2440: '=' : cannot convert from 'void *' to 'float *'
	p1 = (int *)p2;
	
	p2 = p1;
	return 0;
}

d)int *p = NULL;//表示指向地址为0的整型数据

10.指针小结

①变量定义格式和变量的类型:这是一个有意思的东西,去掉变量名就是变量的类型

定义格式类型类型
int aint定义整形变量a
int f()int ()定义返回值为int 的函数
int *pint *
int *p[4]int *[4]指针数组
int (*p)[4]int (*)[4]定义一个指向包含4个元素的一维数组的指针变量
void *pvoid
int *p[4][5]int *[4][5]自己去领悟吧
int (*p)[4][5]int (*)[4][5]自己去领悟吧
void (*p)()int (*)()函数指针

②变量的运算
变量指针的加减一个整数:是这么运算的;
指针变量的赋值:
两个指针变量相减;
两个指针变量的比较;
指针变量的强制赋值。

int main(int argc, char* argv[])
{
	float c = 3.14f;
	int a = 3;
	float *p1 = &c;
	int *p2 = &a;
	int *p3;
	int *p4 = &a;

//	p3 = p2 - p1;//error C2230: '-' : indirection to different types
//	p2 = p1;//error C2440: '=' : cannot convert from 'float *' to 'int *'

//这一点需要注意,p2 -p4,编译器认为结果是int型不是int *型,所以需要强制转换
//但是直接给他有没有问题p2 = p3;
//	p3 = p2 - p4;//error C2440: '=' : cannot convert from 'int' to 'int *'
	p3 = (int *)(p2 - p4);
	p2 = p3;
    return 0;
}
//指针的强制转化
int main(int argc, char* argv[])
{
	void max(int, int);

	void (*p3)(int, int) = max;

	float c = 3.14;
	float *p1 = &c;

	int *p2 = (int *)p1;
	p2 = (int *)p3;//函数指针都可以强制转换,太可怕了

	return 0;
}

void max(int a, int b)
{

}

二、引用

1.什么是引用(C没有,C++有)

①作为动词:使用或者调用
②作为名词:为变量起个别名,共同占用一段地址

2.引用的入门

声明:int &b = a;//是声明,不是定义,**在数据类型后面出现&是引用声明符,其他时候就是地址符;
②注意事项:
a)引用不是一种独立的数据类型,所以只有声明,没有定义;
b)一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象;
c)引用必须在创建时被初始化:①引用作为函数参数时,在函数调用的时候虚实结合实现的,即作为形参的引用时实参的别名;②其他时候在创建的时候初始化int &b = a;。

3.引用主要作用

C++增加引用机制,主要是把它作为函数参数,以扩充函数传递数据的功能。
函数的形参和实参直接传递方式有以下几种:
①值传递:实参是变量(a)或者变量地址(&a),传递的是值;
②地址传递:实参是变量名,传递的是变量的地址。

//引用
int main(int argc, char* argv[])
{
	void max(int &, int &);//函数声明
	int a = 3, b = 5;//变量定义
	int &m = a, &n = b;//声明m,n是a,b的引用

	max(a, b);//实参是变量名,传递的是地址

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
    return 0;
}

void max(int &p1, int &p2)//声明m,n是引用
{
	int m;
	if(p1 > p2)
	{
		m = p1;
		p1 = p2;
		p2 = m;
	}
}

总结

①指针时地址,存储指针值得变量称为指针变量,它的类型称为指针类型,其实时指向类型;
②指针既然是地址,那它的指向相当灵活,这是在定义的时候就确定,在操作的时候体现的,难的是他的操作;
③引用记住是地址传递,实参是变量名,传递的是地址,注意和值传递的区别(这个后续再看看怎么用。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值