C语言学习(四)数组、复合类型

数组

数组的基本语法

什么是数组

  • 数组是 C 语言中的一种数据结构,用于存储一组具有相同数据类型的数据。
  • 数组中的每个元素可以通过一个索引(下标)来访问,索引从 0 开始,最大值为数组长度减 1。

数组的使用

类型 数组名[元素个数];
int arr[5];

演示

#include <stdio.h>

int main(){
	
	
	// 声明一个长度 | 空间为 5 的数组
	// 完全不初始化
	int arr[5];
	
	// 可以通过下标来获取数组的元素
	printf("第0个元素:%d \n" , arr[0]);
	printf("第1个元素:%d \n" , arr[1]);
	printf("第2个元素:%d \n" , arr[2]);
	printf("第3个元素:%d \n" , arr[3]);
	printf("第4个元素:%d \n" , arr[4]);
	
	//通过 [下标] 的方式修改数组中的元素值
	arr[0] = 1;
	arr[1] = 2;
	arr[2] = 3;
	arr[3] = 4;
	arr[4] = 5;
	
	printf("第0个元素:%d \n" , arr[0]);
	printf("第1个元素:%d \n" , arr[1]);
	printf("第2个元素:%d \n" , arr[2]);
	printf("第3个元素:%d \n" , arr[3]);
	printf("第4个元素:%d \n" , arr[4]);
	
	
	
	return 0 ;
}

数组的初始化

  • 在定义数组的同时进行赋值,称为初始化
  • 全局数组若不初始化,编译器将其初始化为零
  • 局部数组若不初始化,内容为随机值

演示

#include <stdio.h>

int main(){
	

	//1. 声明数组并且初始化
	// 完全初始化
	int arr[5]  = {10,20,30,40,50};
	
	//看看数组的元素
	printf("第0个元素:%d\n" , arr[0]);
	printf("第1个元素:%d\n" , arr[1]);
	printf("第2个元素:%d\n" , arr[2]);
	printf("第3个元素:%d\n" , arr[3]);
	printf("第4个元素:%d\n" , arr[4]);
	
	printf("\n\n");
	//-----------------------------------
	//2. 声明数组并且初始化:: 检测是否有设置初始化值,如果有,剩下不赋值,也会自动赋值为0
	// 未完全初始化
	int arr2[5]  = {10};
	
	//看看数组的元素
	printf("第0个元素:%d\n" , arr2[0]);
	printf("第1个元素:%d\n" , arr2[1]);
	printf("第2个元素:%d\n" , arr2[2]);
	printf("第3个元素:%d\n" , arr2[3]);
	printf("第4个元素:%d\n" , arr2[4]);
	
	
	//3. 声明数组并且初始化 :: 也可以不声明数组的长度,由编译器检测元素的个数来取得数组的长度
	int arr3[]  = {10,20,30,40,50};
	
	printf("\n\n");
	
	//看看数组的元素
	printf("第0个元素:%d\n" , arr3[0]);
	printf("第1个元素:%d\n" , arr3[1]);
	printf("第2个元素:%d\n" , arr3[2]);
	printf("第3个元素:%d\n" , arr3[3]);
	printf("第4个元素:%d\n" , arr3[4]);
	
	return 0 ;
}

数组名

  • 数组名是一个地址的常量,代表数组中首元素的地址
#include <stdio.h>

int main(){
	
	
	//1.声明一个长度为5的数组,并且完成初始化: 10,20,30,40,50;
	int arr[] = {10,20,30,40,50};
	
	//2. 获取元素的地址
	printf("第0个元素的地址:%#x\n" , &arr[0]);
	printf("第1个元素的地址:%#x\n" , &arr[1]);
	printf("第2个元素的地址:%#x\n" , &arr[2]);
	printf("第3个元素的地址:%#x\n" , &arr[3]);
	printf("第4个元素的地址:%#x\n" , &arr[4]);
	
	//3. 有一个特殊的点。如果直接操作数组的名字,等于是拿到了首元素的地址。
	printf("直接打印数组的名字:%#x\n" , arr);
	
	
	return 0 ;
}

数组遍历

#include <stdio.h>

int main(){
	
	
	//1.声明一个长度为5的数组,并且完成初始化: 10,20,30,40,50;
	int arr[8] = {10,20,30,40,50};
	
	//2. 打印数组的每一个元素
	for(int i = 0 ; i < sizeof(arr) / sizeof(int) ; i++){
		printf("第%d个元素的值是:%d \n" , i , arr[i]);
	}
	
	printf("\n");
	

	//3. 计算出来数组的长度:
	printf("计算数组的占用总空间:%d\n" , sizeof(arr));
	printf("计算int的占用总空间:%d\n" , sizeof(int));
	printf("数组的长度是:%d\n" , sizeof(arr) / sizeof(int));
	
	return 0 ;
}

数组案例

数组的最大值

#include <stdio.h>

int main(){
	
	
	//1.声明一个长度为5的数组,并且完成初始化
	int arr[] = {10,2,33,114,25};
	
	//2. 具体操作
	
	//2.0 先定义一个数字,可以看成是一个标杆
	int temp = arr[0];
	
	//2.1 遍历每一个元素
	for(int i=1;i<sizeof(arr) / sizeof(int);i++){
		if(arr[i] > temp){
			temp = arr[i];
		}
	}
	
	printf("最大的数:%d \n" , temp);
	
	
	return 0 ;
}

数组的逆置

#include <stdio.h>

int main(){
	
	//1. 定义数组
	int arr[] = {10,20,30,40,50,60};
	
	
	
	//2. 数组的逆置
	
	//2.1 定义左边【开始】 和 右边【结束】的下标|索引
	int left = 0;
	int right = (sizeof(arr) / sizeof(int)) - 1;  // 4
	
	while(left < right){
		
		//交换
		int temp = arr[left];
		arr[left] = arr[right];
		arr[right] = temp;
		
		//变化下标
		left++;
		right--;
	}
	
	//3. 打印数组
	for(int i=0;i<sizeof(arr) / sizeof(int);i++){
		printf("第 %d 个元素是: %d \n" , i , arr[i]);
	}
	
	
	return 0;
}

数组和指针

通过指针操作数组元素

  • 数组名字是数组的首元素地址,但它是一个常量
  • * 和 [] 效果一样,都是操作指针所指向的内存

演示

#include <stdio.h>

int main(){
	
	//1. 定义数组
	int arr[] = {10,20,30,40,50,60};
	
	//2. 简单:
	int *p0 = &arr[0];
	int *p1 = &arr[1];
	int *p2 = &arr[2];
	
	//3. 解引用
	printf("p0解引用:%d\n" ,*p0); // 10
	printf("p1解引用:%d\n" ,*p1); // 20
	printf("p2解引用:%d\n" ,*p2); // 30
	
	
	//========================================
	//4. 数组名字其实就是代表首元素的地址
	int * parr = arr;  // int * parr = &arr[0]
	printf("parr解引用:%d\n" ,*parr); // 10
	printf("parr+1解引用:%d\n" ,*(parr+1)); // 20  此处进行指针偏移,让他指向下一个元素的地址,在对其解引用得到此处元素的值
	printf("parr+2解引用:%d\n" ,*(parr+2)); // 30
	printf("parr+3解引用:%d\n" ,*(parr+3)); // 40
	printf("parr+4解引用:%d\n" ,*(parr+4)); // 50
	
	
	//5. 是否可以直接操作数组的名字呢?【换一个角度看:把数组的名字当成是一个指针来对待!】
//	printf("对arr解引用: %d \n", *arr );
	
	
	/*int a = 10;
	  printf("a的值:%d\n" , a);
	  printf("a的值:%d\n" , 10);
	  
	  int * pa = &a;
	  printf("pa解引用 %d \n" , *pa);
	  printf("pa解引用 %d \n" , *(&a));
	 */
	
	return 0;
}
#include <stdio.h>

int main(){
	
	//1. 简单
	int  a = 10;
	int * pa = &a;
	
	printf("pa解引用:%d \n" , *pa);
	printf("pa解引用:%d \n" , *(&a));
		
	//=============================
	
	printf("\n\n");
	
	//1. 定义数组
	int arr[] = {10,20,30,40,50,60};
	
	//1.1 定义i一个指针指向首元素
	int * p0 = &arr[0];
	for(int i=0;i<6;i++){
		printf("第 %d 个元素的值是: %d \n" ,i , * (p0+i) );
	}
	
	printf("\n\n");
	// 数组名字就是首元素的地址,其实就把它看成一个指针即可。
	for(int i=0;i<6;i++){
		printf("第 %d 个元素的值是: %d \n" ,i , *(arr+i) );
	}
	
	/*printf("(p0+0)解引用: %d\n" , *(p0+0));
	  printf("(p0+1)解引用: %d\n" , *(p0+1));
	  printf("(p0+2)解引用: %d\n" , *(p0+2));
	  printf("(p0+3)解引用: %d\n" , *(p0+3));
	  printf("(p0+4)解引用: %d\n" , *(p0+4));
	  printf("(p0+5)解引用: %d\n" , *(p0+5));*/
	
	return 0;
}

指针数组

  • 指针数组,它是数组,数组的每个元素都是指针类型

演示

#include <stdio.h>

int main(){
	
	
	int a = 10 ;
	int b = 20;
	int c = 30;
	
	int * pa = &a;
	int * pb = &b;
	int * pc = &c;

	//一个数组里面装好多个指针[地址]
	int* arr[3] = {pa ,pb ,pc};
	
	//遍历数组,得到是地址
	for(int i=0;i< 3;i++){
		printf("第 %d 元素是:%#x \n" , i , arr[i]);
	}
	
	printf("\n\n");
	//此处对其解引用,得到的是地址对应的值
	for(int i=0;i< 3;i++){
		printf("第 %d 元素指向位置的值是:%d \n" , i , *arr[i]);
	}
	
	return 0;
}

数组名做函数参数

  • 数组名做函数参数,函数的形参本质上就是指针

演示

#include <stdio.h>


//定义一个函数,用来遍历数组。三种方式都可以 传进来的就是一个地址
//void print_arr(int arr[5] , int size){
//void print_arr(int arr[] , int size){
void print_arr(int * arr , int size){
	
	// 数组的名字单独使用即代表首元素的地址.. 传递进来的就是首元素的地址
	//int size = sizeof(arr) / sizeof(int);
	//printf("函数内部的size: %d \n" , size);
	
	for(int i=0;i< size;i++){
		printf("第 %d 元素是: %d \n" , i ,arr[i]);
	}
}

int main(){
	
	
	//数组在当成参数传递的时候,其实传递过去的是首元素的地址!!!
	int arr[5] = {10,20,30,40,50};
	
	int size = sizeof(arr) / sizeof(int); //在外面没有传递参数的时候是可以计算长度的
	printf("函数外部的size: %d \n" , size);
	
	print_arr(arr , size);
	//print_arr(&arr[0]);
	
	
	return 0;
}

注释:这里要注意的是,数组当成参数传递的时候,传递过去的是首元素的地址。计算数组长度的时候只能在函数外面计算,在函数里面计算就会失真,因为传进去的是地址。所以我们定义函数的时候还要给他一个参数size。

字符数组与字符串

字符串定义

#include <stdio.h>


int main(){
	
	//1. C语言里面没有字符串的类型,只能通过char[] 来描述它。还需要在数组的最后一位写上 \0 才能表示它是字符串
	char c[] = {'a' , 'b' , 'c' , '\0'};
	printf("c=%s \n" , c);
	
	//2. 当字符数组里面出现多个 \0 的时候,认为字符串就是到 第一个 \0 截止
	char c2[]={'a','b','c','\0','d','e','\0'};
	printf("c2=%s \n" , c2);
	
	// 中文在GBK编码下占 2个字节, 在UTF-8的编码下占3个字节
	char c3[]={'你','好','\0'};
	printf("c3=%s \n" , c3);
	
	// 字符串其实还有一种简写的写法:编译器会自动在后面加上 \0 我们不需要加
	char c4[] = "abc";
	printf("c4=%s \n" , c4);
	
	char c5[] = "哈喽你好";
	printf("c5=%s \n" , c5);
	
	
	return 0;
}

字符串的输入输出

#include <stdio.h>


int main(){
	
	//1.获取键盘的输入
	
	printf("请输入您的名字:\n");
	
	char name[20] ;
	
	//scanf 在获取的内容中右空格的时候,就结束获取了。
	//scanf("%s" , name);
	
	// gets本身就是获取字符串的。所以不用写格式
	gets(name);
	
	printf("输入的内容是:%s \n" , name);
	
	printf("\n");
	
	//使用数组的方式,遍历出来每一个字符
	for(int i=0;i< 20;i++){
		if(name[i] == '\0'){
			break;
		}
		printf("第 %d 个元素是: %c \n" , i , name[i] );
	}
	
	
	return 0;
}

字符指针

  • 字符指针可直接赋值为字符串,保存的实际上是字符串的首地址
    • 这时候,字符串指针所指向的内存不能修改,指针变量本身可以修改
#include <stdio.h>

int main() {
    char *p = "hello";  // 和 const char *p = 'hello' 等价,有没有const都一样
    // 指针变量所指向的内存不能修改
    // *p = 'a';    // err
    printf("p = %s\n", p);

    // 指针变量可以修改
    p = "world";
    printf("p = %s\n", p);

    return 0;
}

字符串常用库函数

strlen
strcyp
stecat
strcmp
四种函数演示
#include <stdio.h>
#include <string.h>


int main(){
	
	//1. strlen :: 计算字符串的长度 【有几个字符】
	char c1[] = "abc12";
	printf("c1的长度是:%d \n" , strlen(c1));
	
	//2. strcpy :: 拷贝字符串到目标字符串身上:
	char c2[20] = "ab";
	char c3[10] = "123456789";
	
	// 把c3 拷贝到 c2身上 【会覆盖掉c2的字符串】
	strcpy(c2 , c3);
	printf("c2=%s\n" , c2);
	
	//3. strcat:: 拼接字符串
	char c4[20] = "ab";
	char c5[10] = "123456789";
	strcat(c4 , c5);
	printf("c4=%s\n" , c4);
	
	//4. strcmp:: 比较字符串
	char c6[10] = "abc";
	char c7[10] = "abc";
	
	// strcmp : 返回值 0 表示相等   大于0 表示第一个字符串更大, 小于0 表示第一个字符串更小
	if(  strcmp(c6 ,c7 )  == 0    ){
		printf("相等\n");
	}else{
		printf("不相等~!\n");
	}
	
	return 0;
}

复合类型

结构体

概述

  • 有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性
    • 这时候可通过结构体实现
  • 结构体(struct)可以理解为用户自定义的特殊的复合的“数据类型”

声明结构体

#include <stdio.h>

//1. 定义结构体
struct student{
	char name[20];
	int age ;
	char gender[5];
};

//2. 定义结构体:: 书 【书名、作者、价格】
struct book{
	char name[50];
	char author [20];
	float price;
};

int main(){
	
	return 0;
}

结构体指针

#include <stdio.h>

//1. 定义结构体
struct student{
	char * name ;
	int age ;
	char * gender;
};

int main(){
	
	char * a = "abc";
	
	a = "ccc";
	
	//1. 普通的结构体变量
	struct student s ;
	
	//1.1 后续赋值
	s.name = "张三";
	s.age = 18;
	s.gender = "男";
	
	//1.2 取值
	printf("s.name=%s \n" , s.name);
	printf("s.age=%d \n" , s.age); 
	printf("s.gender=%s \n" , s.gender);
	
	printf("\n--------------------\n");
	
	//2. 结构体指针
	struct student * s2 = &s; 
	
	//2.1 赋值:: 对结构体指针解引用 ,然后就可以继续使用 . 的方式来操作成员了。
	(*s2).name = "李四";
	(*s2).age = 28;
	(*s2).gender ="男";
	
	//2.2 取值
	printf("ss.name=%s \n" , (*s2).name);
	printf("ss.age=%d \n" , (*s2).age);
	printf("ss.gender=%s \n" , (*s2).gender);
	
	printf("\n--------------------\n");
	
	//3. 如果有结构体指针,那么操作结构体内部的成员,可以使用 -> 操作符号 来操作成员
	s2->name = "王五";
	s2->age = 39;
	s2->gender = "女";
	
	printf("s2.name=%s \n" , s2->name);
	printf("s2.age=%d \n" , s2->age);
	printf("s2.gender=%s \n" , s2->gender);
	
	return 0;
}

结构体作函数参数

#include <stdio.h>

//1. 定义结构体
struct student{
	char name[20] ;
	int age ;
	char gender[10];
};


void change_value(struct student * s){ // struct student s = s;
	//s.age = 20;
	s->age = 20;

}

int main(){
	
	//1. 定义变量
	struct student s = {"张三" , 19 , "男"};
	
	//2. 把结构体变量s 传给函数,在函数内部修改年龄
	change_value(&s);
	
	printf("s.name=%s \n" , s.name);
	printf("s.age=%d \n" , s.age);
	printf("s.gender=%s \n" , s.gender);
	
	return 0;
}

共用体(联合体)

共用体的语法

  • 共用体union是一个能在同一个存储空间存储不同类型数据的类型
  • 共用体所占的内存长度等于其最长成员的长度,也有叫做共用体
  • 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
  • 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
  • 共用体变量的地址和它的各成员的地址都是同一地址

#include <stdio.h>

//1. 定义联合体
union Test{
	char a;
	int b;
	short c;
};

int main(){
	
	//1. 创建联合体的变量
	union Test t;
	
	//2. 把大家的地址打一下:
	printf("t的地址:%#x \n" , &t);
	printf("t.a的地址:%#x \n" , &(t.a));
	printf("t.b的地址:%#x \n" , &(t.b));
	printf("t.c的地址:%#x \n" , &(t.c));
	
	//3. 计算联合体的大小。:: 联合体的大小以 最大的成员所占用的空间为准。
	printf("Test所占用的空间:%d \n" , sizeof(union Test));
	
	//4. 给占用空间最大的那个成员赋值,然后一会观察其他的成员有没有值?
	t.b = 0x11223344;
	
	//打印每一个成员的值。
	printf("t.a的值:%#x \n" , t.a);
	printf("t.b的值:%#x \n" , t.b);
	printf("t.c的值:%#x \n" , t.c);
	
	printf("\n\n");
	
	//5. 修改a的值
	t.b = 0x112233ff;
	
	printf("t.a的值:%#x \n" , t.a);
	printf("t.b的值:%#x \n" , t.b);
	printf("t.c的值:%#x \n" , t.c);
	
	return 0;
}

共用体和结构体区别

  • 存储方式:
    • 结构体:结构体中的每个成员都占据独立的内存空间,成员之间按照定义的顺序依次存储
    • 共用体:共用体中的所有成员共享同一块内存空间,不同成员可以存储在同一个地址上
  • 内存占用:
    • 结构体:结构体的内存占用是成员变量占用空间之和,每个成员变量都有自己的内存地址
    • 共用体:共用体的内存占用是最大成员变量所占用的空间大小,不同成员变量共享同一块内存地址

枚举

  • 枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
#include <stdio.h>

// 定义枚举
enum Light{
	RED = 100 , GREEN =200 , YELLOW = 300
};
/*
  // 枚举值默认对应的数值从0 开始
  enum Light{
	  RED , GREEN , YELLOW
  };
 */

int main(){

	//定义枚举的变量
	
	// 声明一个枚举的变量,但是没有初始化
	enum Light light ;
	
	// 声明一个枚举变量,同时初始化
	enum Light light2 = YELLOW ;
	printf("light2 = %d \n" , light2);
	
	return 0;
}

typedef

  • typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

'Magic'

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值