-->

数组的引用与指针

数组的访问

//对普通的数组访问是下标访问的方式
int buf[5] = {1,2,3,4,5};
buf[0] = 8;//这种下标访问的方式

用公式表示就是E1[E2],E1就是数组名,E2就是元素的个数(只能是整数,>=0)

E1[E2] --> *(E1 + E2)这两个公式是一样的,一个是用下标访问,一个是用地址访问
*(E2 + E1) --> E2[E1],这样把元素个数写在外面,数组名写在[]里面也是可以的
多维数组

拿二维数组比喻,int buf[3][4];,无论是多少维的数组,都可以看成数组型数组,意思是数组里面存放着数组

可以看做buf[2]里面的每一个元素都是int [3],然而这个int [3]是一个匿名数组
对于他的公式推导是一样的,先拿出buf[2],然后转换成*(buf + 2)[3]
然后在变成*(*(buf + 2) + 3),以此类推三维以及以上都是如此。
注意上面推导的公式需要定义的是数组指针来访问

使用上面形式的指针来访问数组的每一个元素,需要定义数组指针来接收首元素的地址

int buf[3][4];
int (*p)[4] = buf;//这就是数组指针,()的优先级高

以上就可以用公式来访问数组元素

字符数组中,

char ch[4] = {'a','b','c','d'};//可靠性强,
char ch[4] = "abcd";//最后有一个`\0`,没有给到空间,他会挤出去,挤到没有定义的空间,有很小的肯会报段错误

柔性数组

在C99之前的标准中,是没有这种概念的。就是可以自己输入数组的元素

int num;
scanf("%d",&num);
int buf[num];//没问题

指针

在32bit的系统中,地址都是4个字节的,也就是想要定义一个指针来存储地址的话,那么必须要4个字节,所以就规定了无论用什么类型定义的指针,存放的数据都是4个字节的。

这个数据类型是和偏移的宽度有关的,int型+1就偏移4个字节的地址。

int buf[4] = {1,2,3,4};
int *p = buf;
//p+1 --> 偏移到了 buf[1] 也就是2,所以偏移是4个字节,和定义的数据类型有关

一般的情况下,定义了指针,会将它初始化为NULL也就是空,如果不定义为空,那么他可能回有一些值在里面,然而这些值是很危险的,可能会访问到不能访问的值,导致段错误,也就是野指针。

为了防止野指针,在定义的时候必须初始化为NULL

  • 补充二维数组以及多维数组
    对于这些更高维度的数组,比如二维数组int buf[2][5],可以看成有一个一维数组buf[2],里面的两个元素都存放着一组匿名数组int [5]
    对于更高维度的数组也可以这样看成
/*buf表示的地址是buf[0]的地址,他们两个是一样的关系。但是他们虽然和buf[0][0]的地址一样,他们的性质是不同的。
buf[0][0]代表一小个元素的地址+1的时候,也是只加一小个元素。
但是对于buf和buf[0]来说就是不一样的,他们表示一个元素,但是这个元素里面放的还是数组+1,也是加一个元素,加到buf[1]也就是第二个大元素的第一个小元素的地址。*/
(&buf+1) --> 指向一整个数组的外面,也就是偏移了一整个数组
(&buf[0]+1) --> 指向第二个数组的第一个元素的地址。偏移了一小组数组
(buf+1) --> 和上面的一样
(buf[0]+1) --> buf[0]表示第一个数组的首元素的地址,所以加一就会跳到下一个小元素的地址

上面的结果可以看成数组嵌套了一组数组

函数

  • 耦合度:两个或两个以上的模块(函数)的紧密程度。(越低越好)
  • 内聚度:一个模块内部各部分的紧密程度。(越高越好)
    所以写程序要注意:低耦合,高内聚
    函数名就是他的地址
//指针函数
int function(int a,int b){}

//函数指针
int (*p)(int a,int b){}
//函数指针可以没有函数名

函数的参数

int function(int a,int b){}
//上面的形式括号里面的变量就是形式参数
function(10,20);
function(d,c);
//以上两种方式传进参数,就是实际参数
  • 形式参数 :在定义的时候不会分配空间,但是在调用的时候才会分配空间,函数结束的时候系统会回收空间

参数的传递

//值传递,值传递不会改变b的值,只是把b的值0给传递过去给到a。
void func1(int a)
{
    a = 1;
}

int main(void)
{
    int b = 0;
    func1(b);
    return 0;
}
//地址传递,地址传递会改变b的值,他是把b的地址传递过去操作,地址只有一个
void func2(int *a)
{
    *a = 1;
}

int main(void)
{
    int b = 0;
    func2(&b);
    return 0;
}
//数组传递,以二维数组为例
//当函数参数为数组时,传递函数的首地址即可
void func3(int buf[2][5])
{
    //遍历
    buf[0][0];
}

int main(void)
{
    int buf[2][5] = {0};
    func2(buf);
    return 0;
}

//当函数参数为数组指针时,也是传递函数的首地址即可,但是一般这种情况,为了让数组不越界,需要多传递一个数组的大小进去,以便于遍历数组
void func3(int (*buf)[5])
{
    //遍历
    *(*(buf+0)+0)
}

int main(void)
{
    int buf[2][5] = {0};
    func2(buf);
    return 0;
}

递归

递归是一个算法,一般用于解决数学问题。在函数内部反复的调用自己。

//使用递归来计算1 * 2 * 3 * 4 * 5 * ... * n
int func(int n)//也是要注意返回值,需要什么类型
{
	//使用递归一定要注意终止条件,必须写清楚,要不然会重复调用,占用栈的内存直到溢出,导致段错误。
	if(1 == n){
		return 1;//终止条件
	}
	else{
		return func(n-1)*n;//递归一定是要调用自己的
		/*需要注意的是,传进回来的参数是第几项,也就是最大的,所以需要递减,最主要的是这个函数一次只能返回一个数,所以需要多次调用来组合成一串数字,由于是乘数需要*n
		*/
	}
}

int main()
{
	printf("%d\n",func(5));//120
	return 0;
}

分析过程:在图片里。

image

posted @ 2024-06-14 10:00  wuju  阅读(52)  评论(0)    收藏  举报