【5】C++进阶系列(数组和指针2)

1、实验

实验1:3 x 3矩阵的转置

#include<iostream>

using namespace std;
void swap(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}
int main() {
	int a[3][3];
	cout << "输入9个整数作为矩阵元素值:" << endl;
	for(int i=0;i<3;i++)
		for (int j = 0; j < 3; j++) {
			cin >> a[i][j];
		}
	cout << "初始矩阵:" << endl;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			cout << a[i][j] << " ";
		}
		cout << ' ' << endl;
	}

	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < i; j++) {//j<i是关键,如果是j<3,会再执行一次转置,把转置又转置回去和原来的矩阵一样了。
			swap(a[i][j], a[j][i]);
		}
	}

	cout << "转置矩阵:" << endl;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			cout << a[i][j] << " ";
		}
		cout << ' ' << endl;
	}
	return 0;
}

实验2:声明一个Employee类,其中包括姓名,街道地址。城市和邮编属性。以及change_name()和display()等函数。display()显示姓名,街道地址,城市和邮编等属性;change_name()改变对象的姓名属性。

//Employee.h
#pragma once
#include <string>
#include<iostream>
#ifndef EMPLOY_H_
#define EMPLOY_H_

using namespace std;
class Employee
{
private:
	char const * name;
	char const *roadAdress;
	char const *city;
	char const *email;
public:
	Employee(char const *n="" , char const  *add="", char const *c="", char const *e="" ) :name(n), roadAdress(add), city(c), email(e) {}
	void change_name(char const *n) {
		name = n;
	}
	void display() {
		cout << "name:" << name << endl;
		cout << "address:" << roadAdress << endl;
		cout << "city:"<<city << endl;
		cout << "email:" << email << endl;
	}

};

#endif // !EMPLOY_H_

//test.h
#include<iostream>
#include"Employee.h"

using namespace std;
int main() {
	Employee e("wang sr","haidian","beijing","100840");
	e.display();
	e.change_name("liwei");
	e.display();
	return 0;
}

2、定义指向数组的指针

定义与赋值:int a[10],*p; p=&a[0]或p=a;

等效的形式:*p 就是a[0],*(p+1)就是a[1]……*(p+i)就是a[i]。

所以,这几种写法都是等效的:a[i],*(p+i),*(a+i),p[i]都是等效的。则可以通过数组下标或者指针去访问元素

3、指针数组:数组元素的类型是指针。int *p[7];表示由p[0]-p[6]7个指针组成。

例子:指针数组来存放矩阵

如下:用以为数组的数组名曲初始化指针数组。即:line[1]=line1

#include<iostream>

using namespace std;

int main() {
	int line1[] = { 1,0,0 };
	int line2[] = { 0,1,0 };
	int line3[] = { 0,0,1 };

	int *line[3] = { line1,line2,line3 };
	cout << "Matrix test:" << endl;
	for (int i = 0; i < 3;  i++) {
		for (int j = 0; j < 3; j++) {
			cout << line[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

对比:

                                                              

区别:二维数组的所有元素,是按照行进行连续存放的。指针数组的三个指针所指的位置,不见得是连续存放的。行于行之间可能不是依次连续的。

指针数组和文维数组都可以用来表示一个有行有列的表(矩阵),但是他们是有差别的,差别的核心就在上面。

4、指针与函数(指针作为函数的参数)

函数的参数可以是定义的所有类型的变量,当然也可以是指针。那么问题来了:为什么要用指针来做函数的参数呢?在什么情况下我们需要在函数之间传递指针呢?、

因为:

1、如果以变量的值作为参数,是参数单向传递。如果我们需要双向传递,可以使用引用来进行参数传递,而实际上,传指针也是引用传递的一种。也就是说我们可以把助调函数中已经定义好的变量的地址放在指针里,传给被调函数。这样的话就可以在函数的被调体中就可以通过传过来的指针直接操作汁调函数中的数据(变量)——双向传递。

2、以及有时并不是是很需要双向传递,但也会使用指针——当需要传递一组数据的时候,或则说传递数组的时候。这时我们只需要将数组的收地址传过去,这样一来,传参数的效率的提高了。

例子:读入三个浮点数,将整数部分和小数部分分别输出。

提示:一个函数如果需要返回输出,只能return一个值,但是这里有两个。

#include<iostream>

using namespace std;
//将实数部分分解成为整数部分x和小数部分,形参intPart和fractPart是指针。
//用两个指针来接收,形参是指针用来容纳地址。

//当调用此函数时,实参传递过来的是n和f的地址,相当于把访问授权送过来了。
//所以,对地址的操作就改变了n和f的数据值。通过形参指针实际上操作的是实参指针的值。
void splitFloat(float x, int *intPart, float *fractPart) {
	*intPart = static_cast<int>(x);
	*fractPart = x - *intPart;
}

int main() {
	for (int i = 0; i < 3; i++) {
		float x, f;
		int n;
		cin >> x;
		splitFloat(x, &n, &f);//实参是取的变量的地址
		cout << "整数部分:" << n << "小数部分:" << f << endl;
	}
	return 0;
}

指向常量的指针做形参:

有时既希望传递地址,又不希望主调函数中的数据被修改和破坏。——const指针:指针指向的对象是const常量,通过指针只能读取他所指向的对象,而不能修改。比如只是要输出值而已。

最小授权原则:不仅在传指针的时候,还有其他场合,比如定义类的时候,能尽可能多的隐藏细节或者是保证安全。但是对外的访问授权要一定能够用,但是够用就好,不要过多的授权。

#include<iostream>

using namespace std;
const int N = 6;

void print( const int *p,int n) {
	cout << "{" << *p;
	for (int i = 1; i < n; i++) {
		cout << "," << *(p + i);
	}
	cout << "}" << endl;
}

int main() {
	int array[N];
	for (int i = 0; i < N; i++) {
		cin >> array[i];
	}
	print(array, N);
	return 0;
}

5、指针类型的函数(返回类型):联想到int类型的函数,double类型的函数。表明的是返回值类型

若函数的返回值是指针类型,该函数就是指针函数。其定义形式为:存储类型 数据类型 *函数名(){函数体语句}

但是要注意:不要将非静态局部地址用作函数的返回值。因为:非静态的局部地址离开函数就失效了,如果让主调函数拿着这个地址去访问存储空间是很危险的。例如下图:对不存在的空间进行写操作.。。因为local的作用域和寿命仅限于本函数之内。

                                    

所以,子函数返回的指针要确保在主调函数中依然是和方法和有效的地址。比如说:在主函数中定义了一个数组,将这个数组的地址传递给了子函数,而子函数有一个return该数组的首地址的语句,这样返回的地址就是有效的合法的。因为数组本身就是在主函数中定义的。如下:

                                     

另外:在子函数中通过动态内存分配new操作取得的内存地址返回给主函数是合法有效的,但是内存分配和释放不再同一级别,要注意不要忘记释放,避免内存泄漏new方式获得的内存单元在离开包围他的大括号{……}时,内存是不会自动释放的必须用对应的delete来释放才会消失。否则内存泄漏会带来安全隐患。要求主函数中的delete和子函数中的new要配合好。

                                                

6、指向函数的指针:联想到指向数据的指针。例如指向数组的指针:int a[10],*p; p=&a[0]或p=a;

程序中运行的代码,代码和函数也是有起始地址的。

指向函数的指针:函数指针指向的是程序代码存储区。

定义:存储类型 数据类型 (*函数指针名)();——用小括号()来与指针类型的函数来区别。数据类型是指函数的返回类型。定义指向函数的指针时,要规定这个函数的参数表是什么样的,以及指向的函数是什么类型,函数返回值是怎么样的。

定义指针的时候总要确定他所指向对象的类型。指向数据的指针要确定指向数据的类型;直线该函数的指针要确定指向函数的类型。

问题:每次调用函数都是用的函数名,那么用函数指针有什么用呢?

函数指针的典型用途——实现函数回调。就是说:1、通过函数指针调用函数:例如将函数的指针(地址)作为参数传递给一个函数,使得在处理相似事件的时候可以灵活的使用不同的方法。2、调用者不关心谁是被调用者:需要知道存在一个具有特定原型和限制田间的被调用函数。

举例:编写一个计算函数computer,对于两个整数进行各种计算(通用的,什么计算都能做)。有一个形参是指向具体算法函数的指针,根据不同的实参函数,用不同的算法进行计算。

编写三个函数:求两个整数的最大值,最小值,和。分别用这三个函数作为实参,测试computer函数。

那么肯定有人会问,一个函数怎么可能做各种计算呢?——这个时候,我们在computer的形参中设置一个函数指针,每次调用的时候传递给它一个函数指针。

用函数名去初始化形参函数指针。这样的话,我们写computer函数的时候就只管说我要调用一个对两个整数做运算的函数,至于要做什么运算,只需要把代表这个运算的函数名传给我,我用函数指针来接收,用函数指针充当函数名,去调用函数体。这就是函数指针的好处,也就是函数回调。

#include<iostream>

using namespace std;

int computer(int a, int b, int(*func)(int, int)) {
	return func(a, b);
}

int max(int a, int b) {
	return (a > b ? a : b);
}

int min(int a, int b) {
	return (a > b ? b : a);
}

int sum(int a, int b) {
	return a + b;
}

int main() {
	int a, b, res;
	cout << "请输入整数a:"; cin >> a;
	cout << "请输入整数b:"; cin >> b;
	res = computer(a, b, &max);//地址运算符
	cout << "Max of " << a << " and " << b << " is " << res << endl;
	res = computer(a, b, &min);
	cout << "Min of " << a << " and " << b << " is " << res << endl;
	res = computer(a, b, &sum);
	cout << "Sum of " << a << " and " << b << " is " << res << endl;
	return 0;
}

其实就是函数的形参中传入了另外一个函数的地址。其实形参调用函数名也是ok的,函数名也代表它的地址。

注意区分:指针类型的函数和函数指针两个概念。前者是说函数的返回类型是指针类型,后者是函数名作为指针。

7、对象指针(用来容纳一个对象地址的指针,他指向的是对象)

定义:类名 *对象指针名,例如:

Point a(5,10);

Point *ptr;//说明这个指针式准备用来存放Point地址的

ptr=&a;

怎么访问?——我们用指针去访问所指向的对象要怎么访问呢:访问对象,一般都是想要通过对象去访问对象的成员、公有的接口等。

语法:对象指针名->成员名             例如:ptr->getx()相当于(*ptr).getx(),后者的写法过于繁琐。

#include<iostream>

using namespace std;

class Point
{
public:
	Point(int x, int y) :x(x), y(y) {}
	int getx()const { return x; }
	int gety()const { return y; }
private:
	int x, y;
};

int main() {
	Point a(4, 5);
	Point *p;
	p = &a;
	cout << p->getx() << endl;//4
	cout << p->gety() << endl;//5
	cout << a.getx() << endl;//4
	cout << a.gety() << endl;//5
	return 0;
}

8、this指针。指向当前对象的指针。

问题:假设要调用getx()函数,这时程序中有多个对象,所以应该具体去调用哪个对象的getx()?——this

实际上:this指针隐藏于类的每一个非静态成员函数中。指出成员函数所操作的对象:当通过一个对象调用成员函数的时候,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作的时候,就隐含使用了this指针。

例如:Point类中的getx()函数中的语句:return x;相当于return this->x;

比如我们调用getx函数,按理说进入到函数体内部之后,是不知道执行流程是从哪里进来的,不知道谁调用的它,怎么就能把当前对象的x返回呢?——貌似getx()没有参数,参数表是空的,没有给他传递参数,其实实际上暗地里传给他一个当前对象的地址函数内部隐含有一个指针,指针变量名叫this,当前变量名就传到了this当中。

猜你喜欢

转载自blog.csdn.net/qq_21210467/article/details/82798142