C++学习路线(十三)

发布于:2024-10-17 ⋅ 阅读:(9) ⋅ 点赞:(0)

防御式编程

子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。这种思想是将可能出现的错误造成的影响控制在有限的范围内

具体措施:

对输入进行体检

(1)检查输入源(文件、网络、控制台)数据的合法性

(2)检查每一个参数的合法性

对非预期错误使用断言

(1)空指针

(2)输入输出的的值不在范围内

(3)数组的越界        

防御式编程的做法是 一开始就不引入错误

处理方式:如果断言的条件

        函数原型

#include <assert.h>

void assert(expression);

先计算表达式 如果为假0 先向stderr打印一条信息

然后调用abort

#include <iostream>
#include <assert.h>
using namespace std;

int main() {
	int rows, cols;
	cout << "Enter the number of rows and columns: ";
	cin >> rows >> cols;
	assert(rows > 0 && cols > 0);
	cout << "The matrix is " << rows << "x" << cols << endl;
	return 0;
}

练习一

按照下面的要求修改寻找峰点的程序:
1.打印出网格中峰点的数目。
2.打印出谷点的位置。假定谷点是一个比邻接点海拔都要低的点。编写一个名为isValey 的函数供你的程序调用

3.找出并打印海拔数据中最高点和最低点的位置及其海拔。编写一个名为extremes 的函数供你的程序调用。

3.修改函数isPeak,使用8个邻接点来判断峰点,而不再是只使用4个邻近点判断

#include <iostream>
#include <fstream>
#include <string>
#include <assert.h>
using namespace std;

#define MAX_ROW_SIZE 128
#define MAX_COL_SIZE 128

int dx[8] = {-1,-1,-1,0,0,1,1,1};
int dy[8] = {-1,0,1,-1,1,-1,0,1};
int map[MAX_ROW_SIZE][MAX_COL_SIZE];
int rows, cols;
bool isPeak(int r, int c) {
	bool flag = true;
	for(int i = 0 ; i < 8 ; i ++ )
		if (map[r + dx[i]][c + dy[i]] >= map[r][c]) {
			flag = false;
			break;
		}
	return flag;
}
void printPeak() {
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < cols; j++) {
			if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1) continue;
			if (isPeak(i, j)) {
				cout << map[i][j] << " ";
			}
		}
	cout << endl;
}
bool isVally(int r, int c) {
	bool flag = true;
	for (int i = 0; i < 8; i++)
		if (map[r + dx[i]][c + dy[i]] <= map[r][c]) {
			flag = false;
			break;
		}	
	return flag;
}
void printVally() {
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < cols; j++) {
			if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1) continue;
			if (isVally(i, j)) {
				cout << map[i][j] << " ";
			}
		}
	cout << endl;
}

void printMap() {
	for (int i = 0; i < rows; i++) {
		for (int j = 0; j < cols; j++) {
			cout << map[i][j] << " ";
		}
		cout << endl;
	}
}

void extreme() {
	int highPositionX = -1 , highPositionY = -1, lowPositionX = -1, lowPositionY = -1;
	int highValue = INT_MIN, lowValue = INT_MAX;
	for(int i = 0 ; i < rows ; i ++ )
		for (int j = 0; j < cols; j++) {
			if (map[i][j] > highValue) {
				highValue = map[i][j];
				highPositionX = i;
				highPositionY = j;
			}
			if (map[i][j] < lowValue) {
				lowValue = map[i][j];
				lowPositionX = i;
				lowPositionY = j;
			}
		}
	cout << "High Point: (" << highPositionX << "," << highPositionY << ") Value: " << highValue << endl;
	cout << "Low Point: (" << lowPositionX << "," << lowPositionY << ") Value: " << lowValue << endl;
}

int main() {
	string fileName = "E:\\c++file\\地形图.txt";
	ifstream fin(fileName);
	if (!fin.is_open()) {
		cout << "Open file failed!" << endl;
		return -1;
	}
	fin >> rows >> cols;
	assert(rows <= MAX_ROW_SIZE && cols <= MAX_COL_SIZE);
	assert(rows > 0 && cols > 0);
	for (int i = 0; i < rows; i++)
		for (int j = 0; j < cols; j++)
			fin >> map[i][j];
	fin.close();
	cout << "Map:" << endl;
	printMap();
	cout << "Peaks:" << endl;
	printPeak();
	cout << "Vallies:" << endl;
	printVally();
	cout << "Extreme Points:" << endl;
	extreme();
	return 0;
}

双色球预测系统

1.“双色球”彩票投注区分为红色球号码区和蓝色球号码区。
2.“双色球”每注投注号码由6个红色球号码和1个蓝色球号码组成,红色球号码从1--33 中选择;蓝色球号码从 1--16 中选择,
3.“双色球”每注2元。

项目需求:现在要求我们编写程序找出最近一段时间每个号码出现的次数并把结果保存到一个数组,供其它分析模块调用

指针

为什么要用指针

函数的值传递,无法通过调用函数,来修改函数的实参(因为值传递会把实参拷贝一份给形参 但是指针是把实参地址拷贝给形参 形参通过地址可以操作实参)

void add_money(int money , int add_num) {
	money += add_num;
}

被调用函数需要提供更多的“返回值”给调用函数(c++只允许返回一个参数 如果要返回多个参数的话,我们可以对形参进行操作后 映射到实参 也就是可以用到指针)

// 我们需要返回 money 和 add_num 但是add_num返回不了
int add_money(int money , int add_num) {
	money += add_num;
	return money;
}

减少值传递时带来的额外开销,提高代码执行效率(传指针只用8字节)

struct Money {
	int arr[2048];
	int size;
};
void print_money(Money money) {
	cout << sizeof(money) << endl;
}


int main() {
	Money money;
	money.size = 2048;
	print_money(money);
	return 0;
}

打印出来的结果是8196字节 每一次值传递都要拷贝一次 代价太大了

指针里面存的是变量的地址,通过类型名 *指针变量 = &变量名; 指针变量的值

int a = 2;

int *p = &a;

p的值存的是a的地址 也就是0x(xxxxxxxx)

那么如何取掉a的值呢? 使用解引用*

cout << *p << endl; 就可以取到p指向的值

 令人误解的地方

int *p1 , p2;

p1是指针 而p2是整型变量

指向数组的指针

c语言和c++打印指针地址的方法

#include <iostream>
using namespace std;

int main() {
	int bottle = 50;
	int *bottlePtr = &bottle;
	//c++打印指针指向的变量的地址
	cout << bottlePtr << endl;
	//打印指针本身的地址
	cout << &bottlePtr << endl;
	//使用c语言打印
	printf("%p\n", bottlePtr);
	printf("%p\n", &bottlePtr);
}

指针的初始化

int bottle = 10;
int &ptr = &bottle;

打印指针

int bottle = 10;
int *ptr = &bottle;
printf("%d" , ptr); 不建议用%d十进制来打印ptr 有时候会打出小于0 但是地址是没有小于0的概念
printf("%p" , ptr); 最好使用%p
printf("%x" , ptr); 或者是16进制的%x

上面的结果我们进行分析

首先 第一个负数是因为 我用的是x64架构的系统,也就是说指针是8个字节 如果我用%d指的是int,也就是说超过4字节就会溢出,变成负数

因为我用的是x64,所以说%p输出的是完整64位的地址(16位数字*4字节=64)

而%x只能输出32位的地址 , 也就是说为什么没有前面7e的输出

打印指针指向的值

int bottle = 10;
int *p = &bottle;
*p = 20;
cout << *p << endl;
cout << bottle << endl;

int *p的*是类型说明符 说明p是一个指向int类型的指针

*p的*是间接访问运算符(解引用),作用是访问p指向的内存的地址

空指针和坏指针

#include <iostream>
using namespace std;

int main() {
	int account;
	int roomNumber1 = 666 , roomNumber2 = 777;
	scanf_s("%d\n", &account);
	int* selectPtr;
	if(account == 1) selectPtr = &roomNumber1;
	else if(account == 2) selectPtr = &roomNumber2;

	cout << "Your room number is: " << *selectPtr << endl;
	return 0;
}

上面这种例子就会产生空指针,如果账号account为1,select指针指向roomNumber1

如果account为2 ,select指针指向roomNumber2 那么如果account既不等于1也不等于2

那么selectPtr就为空 , 此时访问空地址指向的值肯定是错的

1.什么是空指针?
空指针,就是值为0的指针。(任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。)
int *p=0;或者int*p=NULL;
2.空指针的使用
1)指针初始化为空指针
int *select=NULL:
目的就是,避免访问非法数据。
2)指针不再使用时,可以设置为空指针int*select = NULL;
3)表示这个指针还没有具体的指向,使用前进行合法性判断

int *p=NULL;
if(p){ //p 等同于 p!=NULL
//指针不为空,对指针进行操作

坏指针

//错误1 尝试使用未初始化的指针
int* ptr;
cout << "ptr is " << *ptr << endl;
//错误2 尝试给指针赋予一个非法地址
ptr = (int*)100;
cout << "ptr is " << *ptr << endl;