C语言:指针详解

news2024/9/9 4:02:24

往期文章

  1. C语言:初识C语言
  2. C语言:分支语句和循环语句
  3. C语言:函数
  4. C语言:数组
  5. C语言:操作符详解

目录

  • 往期文章
  • 前言
  • 1. 指针是什么
  • 2. 指针和指针类型
  • 3. 野指针
  • 4. 指针运算
    • 4.1 指针+-整数
    • 4.2 指针-指针
    • 4.3 指针的关系运算
  • 5. 二级指针
  • 6. 字符指针
  • 7. 指针数组
  • 8. 数组指针
  • 9. 数组参数、指针参数
    • 9.1 一维数组
    • 9.2 二维数组
    • 9.3 一级指针
    • 9.4 二级指针
  • 10. 函数指针
  • 11. 函数指针数组
  • 12. 指向函数指针数组的指针
  • 13. 回调函数
  • 后记

前言

大家好,文章已经更新了五篇了,今天我们来更新第六篇指针。遥记得当年学指针的痛苦,希望这次重新学习能够有别样收获。本博主之前也写过两篇关于指针的文章,也比较详细,不过无论排版还是内容上还是有一些遗憾,希望这篇博客可以弥补之前文章中的遗憾。也欢迎大家点击阅读之前博主关于指针的文章:安得指针千万间大庇天下指针具欢颜上和
安得指针千万间大庇天下地址具欢颜中

1. 指针是什么

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向
(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以
说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址
的内存单元。

计算机是有32位和64位之分的。所谓32位就是有32位地址线,64位就是有64位地址线。每根地址线有正负电之分,我们可以对应为0和1来表示,也就是二进制来表示。
我们以32位的计算机为例,它的表示范围为00000000000000000000000000000000到11111111111111111111111111111111这之中的每一个编号都可以作为一个地址,也是一个字节。

指针变量是用来存放地址的。

#include <stdio.h>
int main()
{
	int a = 10;//在内存中开辟一块空间
	int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
	//将a的地址存放在p变量中,p就是一个之指针变量。
	return 0;
}

在这里插入图片描述
指针指向的是首地址。

在这里插入图片描述

2. 指针和指针类型

虽然指针大小相同,但是指针类型仍然有意义。
定义指针的类型,目的是决定指针访问几个字节。
指针类型不仅可以决定指针访问几个字节,也可以决定指针加一后,跳过几个字节。

在这里插入图片描述

在这里插入图片描述

3. 野指针

在这里插入图片描述
野指针成因:
1.指针未初始化
在这里插入图片描述

2.指针越界访问
在这里插入图片描述

3.指针指向的空间释放
在这里插入图片描述

如何避免野指针

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放即使置NULL
  4. 指针使用之前检查有效性

4. 指针运算

在这里插入图片描述

4.1 指针±整数

#include<stdio.h>

int main()
{
	int arr[5] = { 0 };
	int *p = arr;
	int i = 0;
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d	", *(p + i));
	}	
	return 0;
}

在这里插入图片描述

#include<stdio.h>

int main()
{
	int arr[5] = { 0 };
	int *p = arr;
	int i = 0;
	for (int i = 0; i < 5; i++)
	{
		*p++ = i;
	}
	p = arr;
	for (int i = 0; i < 5; i++)
	{
		printf("%d	", *(p + i));
	}
	return 0;
}



在这里插入图片描述

指针与整数的加减法,有时候可以产生新的地址,有时候可以改变指针指向的地址。

4.2 指针-指针

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);
	return 0;
}

在这里插入图片描述
指针-指针得到的值的绝对值是指针之间的元素个数。我们这里用的是绝对值,因为我们用arr0-arr9会得到同样的结果-9.
在这里插入图片描述
注意,指针减指针的前提是两个指针指向的是同一块连续的空间。

在这里插入图片描述

4.3 指针的关系运算

在这里插入图片描述
在这里插入图片描述
也就是说p是可以和R进行比较滴,但不可以和L进行比较。

5. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针。
在这里插入图片描述

6. 字符指针

有一种指针类型为字符指针,我们通常这样写:

int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

但有些时候,我们会遇到这样的写法:

int main()
{
	char* pstr = "hello.";//这里是把一个字符串放到pstr指针变量里了吗?
	printf("%s\n", pstr);
	return 0;
}

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中
在这里插入图片描述
由此,我们来看一道非常经典的题目:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	char *str3 = "hello bit.";
	char *str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}

这段代码的执行结果是什么呢?
在这里插入图片描述
在这里插入图片描述

7. 指针数组

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

存放指针的数组为指针数组。

#include<stdio.h>
int main()
{
	int a=0;
	int b=5;
	int c=10;
	int *arr[3] = { &a, &b, &c };//指针数组
	return 0;
}

在这里插入图片描述

8. 数组指针

数组指针本质上是指针。能够指向数组的指针。

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合

我们知道数组名是数组的首地址,那么&数组名又是什么呢?

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr= %p\n", &arr);
	printf("arr+1 = %p\n", arr + 1);
	printf("&arr+1= %p\n", &arr + 1);
	return 0;
}

在这里插入图片描述

在这里插入图片描述

注意:除了sizeof(数组名)和&数组名中数组名代表整个数组之外,其他时候数组名都代表数组首元素的地址。
数组指针的使用:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i<row; i++)
	{
		for (j = 0; j<col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i<row; i++)
	{
		for (j = 0; j<col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	printf("*****************************\n");
	print_arr2(arr, 3, 5);
	return 0;
}

在这里插入图片描述
我们再来复习一下数组指针和指针数组:
在这里插入图片描述

9. 数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

9.1 一维数组

对于一维数组,这些传参方式都是可以的:

#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

9.2 二维数组

对于二维数组,这些传参方式都是可以的:

#include<stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int(*arr)[5])//ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。

9.3 一级指针

一级指针传参,以下传参方式是可以的:

#include <stdio.h>
void print(int *p, int sz)
{
	int i = 0;
	for (i = 0; i<sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int *p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

思考:当一个函数的参数部分为整型一级指针的时候,函数能接收什么参数?

  1. 一个整型参数的地址
  2. 一个整型指针
  3. 一个整型数组的数组名

9.4 二级指针

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int*p = &n;
	int **pp = &p;
	test(pp);
	test(&p);
	return 0;
}

思考:当一个函数的参数部分为整型二级指针的时候,函数能接收什么参数?

  1. 取地址一级指针
  2. 二级指针
  3. 每个元素是int*的数组名

10. 函数指针

先来看一段代码:

#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}

在这里插入图片描述
输出的是两个地址,这两个地址是 test 函数的地址。函数指针,就是指向函数的指针。

在这里插入图片描述
函数指针的使用:
在这里插入图片描述

解读两个复杂一些的函数指针:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们可以将代码2简化一下:
在这里插入图片描述
这样就好懂一些了。

11. 函数指针数组

要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10]])();

parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的用途:转移表。这个作用是怎么体现的呢?我们来举一个例子,加入我们要写一个计算器的程序,根据我们之前学过的知识,我们写出的代码是这样的:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}

int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

我们发现,这些加减乘除除了名字不一样,参数啊,返回类型啊都是一样的。我们却还要一个个去调用,有没有什么方法可以简化一下呢,我们看下面的代码:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}


在这里插入图片描述
这样我们的代码就得到了极大的简化。

12. 指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 ,指针指向一个 数组 ,数组的元素都是 函数指针。

定义使用方法如下:

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void(*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void(*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void(*(*ppfunArr)[10])(const char*) = &pfunArr;
	return 0;
}

13. 回调函数

回调函数就是一个通过函数指针调用的函数,如果你把函数指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另一方调用的,用于对该事件或条件进行响应。

举一个例子:
在这里插入图片描述
我们来看上面的那个程序里的test1,它的参数放的是test2,而test2函数里的参数是一个函数指针,在这个程序里,我们并没有直接调用test1,而是把test1的地址传递给了另一个函数test2,然后我们通过p反过来去调用了test1函数,我们称test1函数为回调函数。这个程序可以很好地演示回调函数,我们再来用计算器的程序来演示一下:

#include<stdio.h>
void menu()
{
	printf("欢迎使用计算器>>\n");
	printf("*******************************\n");
	printf("*****      1.add        *******\n");
	printf("*****      2.sub        *******\n");
	printf("*****      3.mul        *******\n");
	printf("*****      4.div        *******\n");
	printf("*****      0.exit       *******\n");
	printf("*******************************\n");
}
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x*y;
}
int div(int x, int y)
{
	return x / y;
}
void cal(int(*p)(int,int))
{
	int a = 0;
	int b = 0;
	int ret = 0;
	printf("请输入两个操作数>>\n");
	scanf("%d %d", &a, &b);
	ret = p(a, b);
	printf("ret=%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择>>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			cal(add);
			break;
		case 2:
			cal(sub);
			break;
		case 3:
			cal(mul);
			break;
		case 4:
			cal(div);
			break;
		case 0:
			printf("退出计算器>>\n");
			break;
		default:
			printf("选择错误>>\n");
			break;
		}
	} while (input);
	return 0;
}


我们增加了cal函数,我们在程序中,将加减乘除的函数名作为cal的参数,从而避免了使用多次重复打相同的代码。

后记

好的,这篇万字长文到这里就结束啦,这绝对是一篇诚意满满的博客,希望对大家有所帮助。感谢大家的关注和支持,我们下一篇博客再见。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/197380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“小灵通”的风雨往事

最近&#xff0c;有一部叫做《狂飙》的国产电视剧火遍全网&#xff0c;相信大家都看到了。在剧中&#xff0c;出现了一个通信名词&#xff0c;不知道在座各位有没有关注到。没错&#xff0c;这个名词&#xff0c;就是“小灵通”。《狂飙》剧的主角高启强&#xff0c;原本是个卖…

Web3.0 · 基础层技术 · SCQA模型趣谈密码学

【小木箱成长营】密码学系列教程&#xff1a; Web3.0 基础层技术 密码学在移动端应用与实践 一、序言 Hello&#xff0c;我是小木箱&#xff0c;欢迎来到小木箱成长营密码学系列教程&#xff0c;今天将分享 Web3.0 基础层技术 SCQA 模型趣谈密码学。 SCQA 模型趣谈密码学主…

第一章 opencv与python介绍及环境搭建

目录1.python安装2.opencv3.pycharm安装4.conda环境搭建(my)1.python安装 网上教程很多就不写了&#xff0c;推荐使用python3.8.2及以上版本 2.opencv opencv简单介绍&#xff1a;opencv是一个开源的计算机视觉库&#xff0c;可以在windows、MacOS、Linux等操作系统上运行。 …

Day878.count(*)问题 -MySQL实战

count(*)问题 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于count(*)问题。 在开发系统的时候&#xff0c;可能经常需要计算一个表的行数&#xff0c;比如一个交易系统的所有变更记录总数。 这时候可能会想&#xff0c;一条 select count(*) from t 语句不就解决…

【自动化测试】从0开始玩转docker—— 02软件配置

目的 CI / CD在目前各类互联网企业中已然成为推动软件开发行为的重要基础设施服务。同样的对于测试团队来说更是有着举足轻重的重大意义&#xff0c;无论是测试左移的具象化提现亦或是持续测试的顺利开展&#xff0c;掌握这一技能已是广大软件测试工程师的必修课。分享这一技术…

第一章:3D点云应用领域分析

&#x1f31e;欢迎来到点云的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; ✉️希望可以和大家一起完成进阶之路&#xff01; &#x1f64f;作者…

力扣(LeetCode)401. 二进制手表(2023.02.03)

二进制手表顶部有 4 个 LED 代表 小时&#xff08;0-11&#xff09;&#xff0c;底部的 6 个 LED 代表 分钟&#xff08;0-59&#xff09;。每个 LED 代表一个 0 或 1&#xff0c;最低位在右侧。 例如&#xff0c;下面的二进制手表读取 “3:25” 。 &#xff08;图源&#xff…

C语言基础知识(56)

下面的C程序的输出是什么#include<stdio.h>intmain(){int a 0;while(a <printf("HI")){a;}return0;}该代码将打印 3 次HI。 printf()函数将返回它正在打印的字符数&#xff0c;并将其与a进行比较。 由于 printf() 的返回值为 2&#xff0c;HI 将被打印 2 次…

字符函数和字符串函数

&#x1f331;博客主页&#xff1a;大寄一场. &#x1f331;系列专栏&#xff1a;C语言学习笔记 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 目录 前言 一、字符串函数 1. 1 strlen 2. 长度不受限制的字符串函数&#xff08;操作的是整个字符串&#…

Linux-用户权限相关命令

1.用户和权限的基本概念1.1基本概念用户是Linux系统工作中重要的一环&#xff0c;用户管理包括用户与组管理在Linux系统中&#xff0c;不论是由本机或是远程登录系统&#xff0c;每个系统都必须拥有一个账号&#xff0c;并且对于不同的系统资源拥有不同的使用权限在Linux中&…

DAMA数据管理知识体系指南之数据操作管理

第6章 数据操作管理 6.1 简介 数据操作管理是结构化数据的开发、维护和支持的活动&#xff0c;使企业数据资源达到最佳的利用价值。数据操作管理包括两项子职能&#xff1a;数据库支持和数据技术管理。 数据操作管理的目标是&#xff1a; &#xff08;1&#xff09;保护和确保…

SpringBoot 如何保证接口安全?老鸟们都是这么玩的!

大家好&#xff0c;我是飘渺。 对于互联网来说&#xff0c;只要你系统的接口暴露在外网&#xff0c;就避免不了接口安全问题。如果你的接口在外网裸奔&#xff0c;只要让黑客知道接口的地址和参数就可以调用&#xff0c;那简直就是灾难。 举个例子&#xff1a;你的网站用户注册…

【NS2学习笔记】tcl与c++互相调用/传参

在NS2&#xff0c;做实验的时候&#xff0c;为了能通过循环配合传值实验&#xff0c;一直找不到tcl传参给c的方法&#xff0c;网上的只po出一部分看不懂&#xff0c;只能通过源码自己研究。最后的解决办法就是&#xff0c;模仿源码的操作&#xff0c;以下通过tcl→ex→sat-irid…

Python如何删除列表中的重复元素?

嗨嗨&#xff0c;大家晚上好 ~ 又来给你们分享小妙招啦 在python列表有重复元素时&#xff0c;可以有以下几种方式进行删除 觉得不错的话&#xff0c;赶紧学起来用用吧 &#xff01; 直接遍历列表删除 l1 [1, 1, 2, 2, 3, 3, 3, 3, 6, 6, 5, 5, 2, 2]for el in l1:if l1.coun…

Java之动态规划的背包问题

目录 动态规划问题 一:01背包问题 1.问题描述 2.分析问题 3.代码实现(二维数组) 4.滚动数组实现(一维数组) 二:完全背包问题 1.题目描述 2.问题分析 3.代码实现 动态规划问题 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题,进行解决&#xff…

若依代码生成器-Domain代码生成篇(一)

若依代码生成器的前一段代码的阅读&#xff0c;我们了解了若依代码生成器的一些逻辑&#xff0c;包括通过数据库的information_schema. TABLES查询表信息&#xff0c;以及information_schema. COLUMNS查询指定表的列信息&#xff0c;将其转换到表gen_table与gen_table_column中…

SETR项目运行记录

项目简要介绍 全称为Swin-Transformer-Semantic-Segmentation&#xff0c;看名字我们就知道使用的是transformer&#xff0c;做的是语义分割方面&#xff0c;使用的数据集是Cityscapes。在本文中我们并不对其原理进行解读&#xff0c;只是调试运行该项目。 项目下载地址&#…

2023年深圳CPDA数据分析师认证将于2/25正式开班,快来报名

CPDA数据分析师认证是中国大数据领域有一定权威度的中高端人才认证&#xff0c;它不仅是中国较早大数据专业技术人才认证、更是中国大数据时代先行者&#xff0c;具有广泛的社会认知度和权威性。 无论是地方政府引进人才、公务员报考、各大企业选聘人才&#xff0c;还是招投标加…

C++这么难,为什么我们还要学习C++?

文章目录前言1. 为什么难学2. C的意义3. 什么时候该用C4. 如何学习C5. 学前勉言前言 C 可算是一种声名在外的编程语言了。这个名声有好有坏&#xff0c;从好的方面讲&#xff0c;C 性能非常好&#xff0c;哪个编程语言性能好的话&#xff0c;总忍不住要跟 C 来单挑一下&#x…

[架构之路-98]:《软件架构设计:程序员向架构师转型必备》-8-确定关键性需求与决定系统架构的因素

第8章 确定关键性需求是什么决定了软件系统的架构&#xff1f;&#xff01;没有大的争议的是&#xff1a;需求决定了软件系统的架构&#xff01;那么什么样的需求对软件系统的架构影响最大&#xff1f;8.1 众说纷纭——什么决定了架构8.1.1 用例驱动论&#xff1a;功能性需求用…