C语言零基础--进阶数组+指针--系统学习5day

目录

1.数组名的含义

2.数组下标

3.字符串常量(匿名数组)

4.零长数组(结构体空间扩充)

5.变长数组

6.char型指针

7.多级指针

8.指针万能拆解法

9.void型指针

10.const型指针

11.函数指针巧用

12.练习题

1.字节序

2.数组下标运算、指针运算、内存操作

3.复杂声明,多维数组操作

4.数组操作

5.复制数组

6.字符去重


1.数组名的含义

        数组名a有两个含义:

        第一含义是:整个数组(在数组定义中 || 在 sizeof 运算表达式中 || 在取址符&中)
        第二含义是:首元素地址(其他任何情形下,即指向首元素的指针

        实例:

int a[3];                  // 此处,a 代表整个数组
printf("%d\n", sizeof(a)); // 此处,a 代表整个数组
printf("%p\n", &a);        // 此处,a 代表整个数组,此处为整个数组的地址

int *p = a;       // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
p = a + 1;        // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
function(a);      // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]

        注意:C语言只有在第一含义的场合下表现为数组,其他大部分场合都表现为首元素的地址,当数组表现为首元素地址时,实际上就是一个指向其首元素的指针。数组运算实际上就是指针运算。

2.数组下标

数组下标实际上是编译系统的一种简写.
其等价形式是: a[i] = 100; 等价于 *(a+i) = 100;
根据加法交换律,以下的所有的语句均是等价的:

  a[i] = 100;
*(a+i) = 100;
*(i+a) = 100;
  i[a] = 100;

         数组运算,等价于指针运算

例如:

int a[3];
// 此处的a等价于 &a[0],而a[0]是一个int,因此此处a的类型是 int (*)
int *p = a+1;

int a[2][3];
// 此处的a等价于 &a[0],而a[0]是一个int[3],因此此处a的类型是 int (*)[3]
int (*p)[3] = a+1;

int *a[3];//相当于二级指针,int **a;
// 此处的a等价于 &a[0],而a[0]是一个int *,因此此处a的类型是 int (*)*
int **p = a+1;

3.字符串常量(匿名数组)

        字符串常量在内存中的存储,实质是一个匿名数组

        匿名数组,同样满足数组两种涵义的规定

        实例:

printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
printf("%p\n", &"abcd");        // 此处 "abcd" 代表整个数组

printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址
char *p1 = "abcd";         // 此处 "abcd" 代表匿名数组的首元素地址
char *p2 = "abcd" + 1;     // 此处 "abcd" 代表匿名数组的首元素地址

4.零长数组(结构体空间扩充)

        用途:放在结构体的末尾,作为可变长度数据的入口

        实例:

struct node
{
    /* 结构体的其他成员 */
    // 成员1
    // 成员2
    // ... ...
    
    int   len;
    char *data[0];
};
// 给结构体额外分配 10 个字节的内存。
struct node *p = malloc(sizeof(struct node) + 10);
p->len = 10;

// 额外分配的内存可以通过 data 来使用
p->data[0] ~ p->data[9]

5.变长数组

        概念:定义时,使用变量作为元素个数的数组。
        要点:变长数组仅仅指元素个数在定义时是变量,而绝非指数组的长度可长可短。实际上,不管是普通数组还是所谓的变长数组,数组一旦定义完毕,其长度则不可改变。

        实例:

int len = 5;
int a[len];  // 数组元素个数 len 是变量,因此数组 a 是变长数组

int x = 2;
int y = 3;
int b[x][y]; // 数组元素个数 x、y 是变量,因此数组 b 是变长数组
int b[2][y]; // 数组元素个数 y 是变量,因此数组 b 是变长数组
int b[x][3]; // 数组元素个数 x 是变量,因此数组 b 是变长数组

        语法:变长数组不可初始化,即以下代码是错误的:

int len = 5;
int a[len] = {1,2,3,4,5}; // 数组 a 不可初始化

6.char型指针

        char型指针实质上跟别的类型的指针并无本质区别,但由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。

        定义: 

char *p = "abcd";

7.多级指针

        如果一个指针变量 p1 存储的地址是另一个普通变量 a 的地址,那么称 p1 为一级指针

        如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针

        如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针

        以此类推,p2、p3等指针被称为多级指针
        示例: 

int a = 100;
int   *p1 = &a;  // 一级指针,指向普通变量
int  **p2 = &p1; // 二级指针,指向一级指针
int ***p3 = &p2; // 三级指针,指向二级指针

8.指针万能拆解法

        任意的指针,不管有多复杂,其定义都由两部分组成。
        第1部分:指针所指向的数据类型,可以是任意的类型
        第2部分:指针的名字

        实例:

char   (*p1);      // 第2部分:*p1; 第1部分:char; 
char  *(*p2);      // 第2部分:*p2; 第1部分:char *; 
char **(*p3);      // 第2部分:*p3; 第1部分:char **; 
char   (*p4)[3];  // 第2部分:*p4; 第1部分:char [3]; 
char   (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float); 

         注意:上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
                    上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的数据类型不同
                    第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

9.void型指针

        概念:无法明确指针所指向的数据类型时,可以将指针定义为 void 型指针
        要点: void 型指针无法直接索引目标,必须将其转换为一种具体类型的指针方可索引目标
                    void 型指针无法进行加减法运算

        void关键字三个作用:

1.修饰指针,表示指针指向一个类型未知的数据。

2.修饰函数参数列表,表示函数不接收任何参数。(预习)int main(void)
3.修饰函数返回类型,表示函数不返回任何数据。(预习)void func(int)

// 指针 p 指向一块 4 字节的内存,且这4字节数据类型未确定
void *p = malloc(4);

// 1,将这 4 字节内存用来存储 int 型数据
*(int *)p = 100;
printf("%d\n", *(int *)p);

// 2,将这 4 字节内存用来存储 float 型数据
*(float *)p = 3.14;
printf("%f\n", *(float *)p);

10.const型指针

        const型指针有两种形式:①常指针 ②常目标指针

常指针:const修饰指针本身,表示指针变量本身无法修改。
常目标指针:const修饰指针的目标,表示无法通过该指针修改其目标。 常指针在实际应用中不常见。 常目标指针在实际应用中广泛可见,用来限制指针的读写权限
int a = 100;
int b = 200;

// 第1中形式,const修饰p1本身,导致p1本身无法修改
int * const p1 = &a; 

// 第2中形式,const修饰p2的目标,导致无法通过p2修改a
int const *p2 = &a;
const int *p2 = &a;

11.函数指针巧用

        概念:指向函数的指针,称为函数指针。
        特点:函数指针跟普通指针本质上并无区别,只是在取址和索引时,取址符和星号均可省略

        示例:

void   f (int); // 函数 f 的类型是: void (int)
void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数

p = &f; // p 指向 f(取址符&可以省略)
p =  f; // p 指向 f

// 以下三个式子是等价的:
  f (666); // 直接调用函数 f
(*p)(666); // 通过索引指针 p 的目标,间接调用函数 f
 p (666); // 函数指针在索引其目标时,星号可以省略

        要点: 函数指针是一类专门用来指向某种类型函数的指针。

                    函数的类型不同,所需要的函数指针也不同。
                    函数的类型,与普通变量的类型判定一致,即去除声明语句中的标识符之后所剩的语句。

12.练习题

1.字节序

编写一个程序,测试当前平台的字节序。

解析: 此题有多种解法,在本次课中,我们可以使用指针来操作某个特定的字节。

#include 

int main(void){
    // 定义一个4字节的整型数据
    int a = 0x12345678;

    // 定义一个char型指针指向最低地址
    char *p = &a;

    // 将最低字节数据打印出来
    // 如果是0x78,那就代表最低地址存储了低有效位,是小端序
    // 如果是0x12,那就代表最低地址存储了高有效位,是大端序
    printf("%#x\n", *p);
}

2.数组下标运算、指针运算、内存操作

分析以下代码的输出结果

#include 
int main(void){
    long  a[4] = {1, 2, 3, 4};
    long  *p1=(long *)(&a+1);
    long  *p2=(long *)((long)a+1);
 
    printf("%lx\n", p1[-1]);
    printf("%lx\n", *p2);
    
    return 0;
}

 

首先,&a是整个数组的地址,因此&a+1实际上是向后偏移了16个字节,因此 p1 指向了数组边界。然后输出 p1[-1] 等价于*(p1-1),因为 p1 是 long 型指针,因此 p1-1 就向前偏移 sizeof(long) 个字节,也就是指向了 a[3]。

其次,(long)a+1 是一个纯整数运算(因为a被强转为long了),因此 p2 就指向了long型数据 a[0] 的第二个字节,最后打印*p2 时,由于 p2 是一个 long 型指针,系统会从 a[0] 的第二个字节开始,取出 sizeof(long) 个字节出来作为 long 型数据来解释,因此最后输出的结果是 a[0] 的高位三字节和 a[1] 低位一字节的数据。

3.复杂声明,多维数组操作

编写程序,声明一个二维 int 型数组 a,再声明另一个一维数组指针数组 b,使该数组 b 的每一个指针分别指向二维数组 a中的每一个元素(即每一个一维数组),然后利用数组 b 计算数组 a 的和。

//声明一个二维 int 型数组 a,再声明另一个一维数组指针数组 b,使该数组 b 的每一个指针分别指向二维数组 a
//中的每一个元素(即每一个一维数组),然后利用数组 b 计算数组 a 的和。
#include
int main(){

    int a[2][4]={{1,2,3,4},{5,6,7,8}};
    int (*b[2])[4];

    b[0]=&a[0];
    b[1]=&a[1];

    int sum=0;
    for(int i=0;i<2;i++){
        for (int j = 0; j < 4; j++){
            
            sum=sum+(*b[i])[j];
        }
    }  
printf("数组a的和sum=%d\n",sum);
}

4.数组操作

编写一个程序,求一个有N个元素的整型数组中子数组之和的最大值,子数组指的是一个数组中连续的若干个相邻的元素。 例如:

int a[7] = {-2, 5, -1, 6, -4, -8, 6}; 则子数组之和的最大值是 5+(-1)+6,即答案是 10。

解析: 本题是一道笔试题,考的是算法。因此不宜使用三重循环的暴力破解法。 参考代码:

#include 

int main(void){
	int a[100];
	int len = 0;

    printf("请输入系列整数,以#结束\n");
	while(scanf("%d", &a[len]) != 0)
		len++;

	int max=a[0], sum=0;

	int i;
	for(i=0; i max)
			max = sum;

		else if(sum < 0)
			sum = 0;
	}

	printf("最大子数组之和: %d\n", max);
	return 0;
}

5.复制数组

【5】编写一个函数,它接收两个 m×n 的整型二维数组 a 和 b,
函数的功能是将数组 a 中的数据复制到数组 b 中。

参考代码:

#include
//写一个函数,它接收两个 m×n 的整型二维数组 a 和 b,
//函数的功能是将数组 a 中的数据复制到数组 b 中。

void cv(int m,int n,int a[m][n],int b[m][n]){
    for (int i = 0; i < m; i++){
        for(int j=0;j

6.字符去重

编写一个程序,去掉给定字符串中重复的字符。 例如:

输入:google 输出:gole

// 6、编写一个程序,去掉给定字符串中重复的字符。
// 例如给定”google”,输出”gole”。(华为笔试题)
#include 
#include 
 
int main(int argc, char const *argv[])
{
	char arr[20];
	char ayy[20];//接收没有重复的字符串
 
	printf("请输入一个字符串:\n");
	scanf("%s", arr);
 
 
	//获取arr字符串长度(不包括后面的'\n');
	int len = strlen(arr);
 
	//定义统计数据
	int sum = 0;
 
	//定义两个指针分别指向他们
	char *p = arr;
	char *q = ayy;
 
	int k = 0;
 
 
	// 判断
	for (int i = 0; i < len; i++){
		sum = 0;
		for (int j = 0; j < i; j++){
			if (*(p + i) == *(p + j))
				sum++;
		}
 
		printf("sum = %d\n", sum);
 
		if(sum == 0){
			*(q + k) = *(p + i);
			k++;
		}
	}
 
	printf("%s\n", ayy);
 
	return 0;
}

 

你可能感兴趣的:(C语言入门到深入,c语言,学习,数据结构)