菜鸟c教程2

数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变
量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。
所有的数组都是由连续的内存位置组成。
//二维数组存放字符串,读取时当一维数组使用。比如:
#include<stdio.h>
int main(){
  int i;
  char names[6][50]={"马超","关平","赵云","张飞","关羽","刘备"};
  for(i=0;i<6;i++)  {
      printf("悍将名称:%s\n",names[i]);
  }
  return 0;
}

步长,二维数组名的步长就是一行的长度,而元素的步长就是一个元素的长度 

二维数组在逻辑上是方阵,由行和列组成。
但是二维数组在物理上是线性的,按行来依次进行存放,内存是连续的。
二维数组名的步长是一行的长度,比如一下例子中:
age + 1 address is 00EFFC04
age + 2 address is 00EFFC14
因为每一行有四个元素,每个int类型的元素占四个字节,一行有16个字节,所以数组名age加1后地址增加了16个字节说明数组名的步长位一行的长度。
具体到每一个元素加1的时候,地址增加的是一个元素所占字节的大小,因此元素的步长即为元素本身的大小,例如:
age[2][0] + 0 address is 00EFFC14
age[2][0] + 1 address is 00EFFC18
#include <stdio.h>

int main()
{
    int age[6][4];
    for (int i = 0; i < sizeof(age)/sizeof(age[0]) ; i++)
    {
        printf("age + %d address is %p\n",i, age + i);
    }
    for (int i = 0; i < sizeof(age) / sizeof(age[0]); i++)
    {
        for (int j = 0; j < sizeof(age[0]) / sizeof(int); j++)
        {
            printf("age[%d][0] + %d address is %p\n",i,j,&age[i][0]+j);
        }

    }
}
输出结果:
age + 0 address is 0x7fffd98b9400
age + 1 address is 0x7fffd98b9410
age + 2 address is 0x7fffd98b9420
age + 3 address is 0x7fffd98b9430
age + 4 address is 0x7fffd98b9440
age + 5 address is 0x7fffd98b9450
age[0][0] + 0 address is 0x7fffd98b9400
age[0][0] + 1 address is 0x7fffd98b9404
age[0][0] + 2 address is 0x7fffd98b9408
age[0][0] + 3 address is 0x7fffd98b940c
age[1][0] + 0 address is 0x7fffd98b9410
age[1][0] + 1 address is 0x7fffd98b9414
age[1][0] + 2 address is 0x7fffd98b9418
age[1][0] + 3 address is 0x7fffd98b941c
age[2][0] + 0 address is 0x7fffd98b9420
age[2][0] + 1 address is 0x7fffd98b9424
age[2][0] + 2 address is 0x7fffd98b9428
age[2][0] + 3 address is 0x7fffd98b942c
age[3][0] + 0 address is 0x7fffd98b9430
age[3][0] + 1 address is 0x7fffd98b9434
age[3][0] + 2 address is 0x7fffd98b9438
age[3][0] + 3 address is 0x7fffd98b943c
age[4][0] + 0 address is 0x7fffd98b9440
age[4][0] + 1 address is 0x7fffd98b9444
age[4][0] + 2 address is 0x7fffd98b9448
age[4][0] + 3 address is 0x7fffd98b944c
age[5][0] + 0 address is 0x7fffd98b9450
age[5][0] + 1 address is 0x7fffd98b9454
age[5][0] + 2 address is 0x7fffd98b9458
age[5][0] + 3 address is 0x7fffd98b945c
可以看到每一个元素占了四个字节的大小,并且这24的元素的地址是连续的。
将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维的,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。事实上,编译器是这样处理数组的:
设有数组 int a[m][n],如果要访问 a[i][j ]的值,编译器的寻址方式为。
&a[i][j]=&a[0][0]+i*sizeof(int)*n+j*sizeof(int); // 注意 n 为第二维的维数
因此,可以省略第一维的维数,不能省略其他维的维数。
在定义二维数组的时候对其进行初始化,也可以省略第一维,编译器会根据你的初始化语句自动决定第一维度。
实例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    char a[10][10];
    memset(a,0,sizeof(a));
    printf("%lu\n",sizeof(a));
    for(int i=0;i<10;i++)
    {
        for(int j=0;j< 10;j++)
            printf("%d ",a[i][j]);
    }
    //system("PAUSE");
    return 0;
}
执行输出结果:
100
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
//三种方式数组传参
void myFunction(int *param)

void myFunction(int param[10])

void myFunction(int param[])

这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一
个多维数组作为形式参数。

如果我们想将二维数组作为实参传递给某个函数,如下代码是有问题的: 
double * MatrixMultiple(double a[][], double b[][]);
原因可以简单理解为:编译器并没有那么高级,在二维以上的数组一定要规定一个最高维数:
double * MatrixMultiple(double a[][2], double b[][3]);  /* 这才是正确的 */



#include<stdio.h>
int main(void)
{
	void print_c(int *a, int n, int m);
	int a[5][5] = { { 1, 2 }, { 3, 4, 5 }, { 6 }, { 7 }, { 0, 8 } };

	printf("\n方法3:\n");
	print_c(&a[0][0], 5, 5);

	return 0;
}
void print_c(int *a, int n, int m) {
	int i, j;
	for (i = 0; i < n; i++) {
		for (j = 0; j < m; j++)
			printf("%d ",*(a + i*m + j));
		printf("\n");
	}
}

结果:
方法3:
1 2 0 0 0
3 4 5 0 0
6 0 0 0 0
7 0 0 0 0
0 8 0 0 0
请按任意键继续. . .


C 语言不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。

如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
int * myFunction(),另外,C 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。

srand((unsigned)time(NULL))是初始化随机函数种子:
 1、是拿当前系统时间作为种子,由于时间是变化的,种子变化,可以产生不相同的随机数。计算机中的随机数实际上都不是真正的随机数,如果两次给的种子一样,是会生成同样的随机序列的。 所以,一般都会以当前的时间作为种子来生成随机数,这样更加的随机。
 2、使用时,参数可以是unsigned型的任意数据,比如srand(10); 
 3、如果不使用srand,用rand()产生的随机数,在多次运行,结果是一样的。
//这里传地址+1的话就会默认加上整个数组的长度
#include<stdio.h>
int main(void)
{
	int a[10];
	printf("%p\n", &a);
	printf("%p", &a+1);
	return 0;
}

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


两种方法打印出来的地址都是相差40个字节
但是
#include<stdio.h>
int main(void)
{
	int a[10];
	printf("%p\n", a);
	printf("%p", a+1);
	return 0;
}

这种方式打印出来的值就是4个字节,也就是说直接写a和&a的意义是不一样的
a指的就是数组a[0]地址,也就是首地址,但是&a也是数组地址,指的是数组地址,这个地址也指向数组的首地址。a与&a虽然数值相等,但是代表的意义意义不相等a的步长就是数组元素的长度,&a就是整个数组的长度。

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个
枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:
enum season {spring, summer=3, autumn, winter};
没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

当然前面的枚举类型也是可以省略的
enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

枚举类型要是连续就可以实现遍历,但是不连续的话就不可以实现遍历


枚举其实可以直接使用,上代码:
#include <stdio.h>
#include <stdlib.h>

enum {
 Q,W,E=4,R
};

int main()
{

   printf("枚举值QWER分别是: %d , %d , %d , %d",Q,W,E,R);
   
   return 0;
}
输出:
枚举值QWER分别是: 0 , 1 , 4 , 5
NULL 指针是一个定义在标准库中的值为零的常量

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 
有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假
定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句

所有指针在创建时都要初始化,如果不知道他指向什么就将 0 赋值给他。必须初始化指针,没有被初始化的指针
被称为失控指针(野指针)。

指针的一些复杂说明:
 int p; -- 这是一个普通的整型变量
 int *p; -- 首先从 p 处开始,先与*结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
 int p[3] -- 首先从 p 处开始,先与[] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
 int *p[3]; -- 首先从 p 处开始, 先与 [] 结合, 因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。
 int (*p)[3]; -- 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。
 int **p; -- 首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
 int p(int); -- 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明函数的返回值是一个整型数据。
 int (*p)(int); -- 从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
 int *(*p(int))[3]; -- 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。


int board[8][8];    /* int 数组的数组 */ 
int ** ptr;         /* 指向 int 指针的指针 */
int * risks[10];    /* 具有 10 个元素的数组, 每个元素是一个指向 int 的指针 */
int (* rusks) [10];  /* 一个指针, 指向具有 10 个元素的 int 数组 */
int * oof[3][4];    /* 一个 3 x 4 的数组, 每个元素是一个指向 int 的指针 */ 
int (* uuf) [3][4]; /* 一个指针, 指向 3 X 4 的 int 数组 */
int (* uof[3]) [4]; /* 一个具有 3 个元素的数组, 每个元素是一个指向具有 4 个元素的int 数组的指针 */ 
free() 后指针不赋 NULL,为指针分配内存后,指针便可以指向一片合法可使用的内存,但使用 free() 释放那
片内存时,指针依旧存放着那片内存的地址,也就是依旧指向那片内存,但这片内存已经释放,不可访问,这时若
不小心使用了这个指针,便会内存错误,又是会有奇怪的 bug ,代码几百行多点就会难以调试,业界给这样的指
针也有个统称:“悬空指针”,为了避免这种蛋疼的情况出现,一定要释放内存后,给指向这片内存的指针,都赋值
为 NULL,从中也可以看出,free() 这个函数释放内存时跟指向这片内存的指针并没有什么卵关系,不会连着把
指针一起搞定掉的! 珍爱生命,远离 "野指针" 与 "悬垂指针" !

野指针:未初始化的指针
悬垂指针:free后没使他指向NULL的指针
只要不取地址&一切对数组的操作的丢向都是元素,&之后&a,操作就是对整个数组。

&a[0],这样的操作针对的也是元素,总结就是&数组名,比较独特。
//函数指针普通用法举例
#include <stdio.h>
 
int max(int x, int y)
{
    return x > y ? x : y;
}
 
int main(void)
{
    /* p 是函数指针 */
    int (* p)(int, int) = & max; // &可以省略
    int a, b, c, d;
 
    printf("请输入三个数字:");
    scanf("%d %d %d", & a, & b, & c);
 
    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c); 
 
    printf("最大的数字是: %d\n", d);
 
    return 0;
}
//回调函数
回调函数 
函数指针作为某个函数的参数
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就
打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给
店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店
里去取货叫做响应回调事件。 
//回调函数举例
#include <stdlib.h>  
#include <stdio.h>
 
// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}
有关于 size_t:
size_t 是一种数据类型,近似于无符号整型,但容量范围一般大于 int 和 unsigned。这里使用 size_t 是为
了保证 arraysize 变量能够有足够大的容量来储存可能大的数组。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值