C语法Day3-结构
# 数组
# 一维数组
// 声明数组,需指定数量
double balance[10];
/*
数组分配空间的时候是连续的内存地址,所声明和初始化时需要知道最大容量。
数组内存空间是连续的,通常以下标0开始,因为地址连续,可以通过下标找到对应的数据。
*/
// 初始化数组
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
或者
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 赋值
balance[4] = 50.0;
// 取值
double salary = balance[3];
# 多维数组
// 声明 一个二维数组,包含 3 行和 4 列:
int x[3][4];
// 初始化二维数组
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
// 内部嵌套的括号是可选的,下面的初始化与上面是等同的:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
// 访问二维数组元素,第3行,第4列
int val = a[2][3];
// 声明创建了一个三维 5 . 10 . 4 整型数组:
int threedim[5][10][4];
# 传递数组参数给函数
以下三种声明方式的结果是一样的:
1、形式参数是一个指针
void myFunction(int *param) { // 如果实际传递对象类型不一致会报错
...
}
2、形式参数是一个已定义大小的数组
void myFunction(int param[10]) {
...
}
3、形式参数是一个未定义大小的数组:
void myFunction(int param[]) {
...
}
# 从函数返回数组
数组做参数时可以直接声明使用数组也可以使用指针,但是,返回值声明只能使用指针
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* 函数要生成和返回随机数的数组r */
int * getRandom() {
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i) {
r[i] = rand();
}
return r;
}
/* 要调用上面定义函数的主函数 */
int main () {
int *p; /* 一个指向整数的指针 */
int i;
p = getRandom();
for ( i = 0; i < 10; i++ ) {
printf( "*(p + %d) : %d\n", i, *(p + i));
}
return 0;
}
# 枚举 Enum
// 不用枚举可以使用宏定义一些常量
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
-------- 声明 --------
// 枚举语法声明格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
// 声明DAY 枚举,表示星期
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
// 默认0开始递增,可以自定义值 : summer=3
enum season {spring, summer=3, autumn, winter};
--------- 定义 ---------
// 定义一个枚举变量
enum DAY day;
// 定义枚举类型DAY的同时,定义枚举变量day 【这里也可以省略 DAY,建议加上】
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
// 结合switch
switch (day)
{
case SAT:
printf("星期六");
break;
case SUN:
printf("星期天");
break;
default:
printf("工作日");
}
--------- 强制转换 ---------
// 将整数转换为枚举:
int a = 6;
enum DAY weekend;
weekend = (enum DAY) a; //类型转换
//weekend = a; //错误
printf("weekend:%d",weekend);
# 回调函数
回调函数:函数的入参是 函数指针,这样在一个函数内部能拿到其它函数,并进行调用。
#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];
/* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
# 字符串
在 C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。
空字符(Null character)又称结束符,缩写 NUL,是一个数值为 0 的控制字符,\0 是转义字符,意思是告诉编译器,这不是字符 0,而是空字符。
下面的声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符 \0,所以字符数组的大小比单词 RUNOOB 的字符数多一个。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B'}; // 长度至少比值长度大一,最后要留一个位置给编译器添加
char site[] = "RUNOOB";
以下是 C/C++ 中定义的字符串的内存表示:
字符串的函数:
序号 | 函数 & 目的 |
---|---|
1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
3 | strlen(s1); 返回字符串 s1 的长度。 |
4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 |
5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
测试:
#include <stdio.h>
#include <string.h>
int main ()
{
char str1[14] = "runoob";
char str2[14] = "google";
char str3[14];
int len ;
/* 复制 str1 到 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\n", str3 );
/* 连接 str1 和 str2 */
strcat( str1, str2);
printf("strcat( str1, str2): %s\n", str1 );
/* 连接后,str1 的总长度 */
len = strlen(str1);
printf("strlen(str1) : %d\n", len );
return 0;
}
// 结果
strcpy( str3, str1) : runoob
strcat( str1, str2): runoobgoogle
strlen(str1) : 12
# 结构体
数组:允许定义可存储相同类型数据项的变量,
**结构体:**是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
# 创建结构体:
---- 1、声明结构体,声明变量 ----
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
---- 2、声明结构体,同时声明变量 ----
// 或者直接声明变量
struct SIMPLE
{
int a;
char b;
double c;
} t1;
---- 3、声明结构体,同时声明变量,但是没有标签 ----
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
⚠️ 注意: 这里的s1 不等于 前面的t1
---- 4、自定义结构体,声明变量 ----
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
---- 5、结构体包含自己 ----
// 结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
//此结构体的声明包含了指向自己类型的指针,典型的链表结构
struct NODE
{
char string[100];
struct NODE *next_node; // 链表,包含自己
struct SIMPLE a; // 也可以包含其它结构体
};
# 结构体互相包含
则需要对其中一个结构体进行不完整声明,如下所示:
struct B; // ⚠️ 对结构体B进行不完整声明,否则结构体A无法使用B做属性变量
//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};
# 结构体变量初始化:
#include <stdio.h>
// 结构体声明必需在使用结构体函数之前声明,不能在函数的后面,
// 函数内调用的结构体必需在其前面先声明,不能通过 结构体不完整声明编译成功。
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
⚠️ 注意点:
1、结构体声明时可以使用后面定义的结构体,只需要提前 声明不完整结构体
2、函数中使用的结构体,必需已经声明,不能是不完整声明
3、函数中使用的其他函数,可以使用后面实现的函数,但是需要在之前进行方法声明。
# 访问结构的成员
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
// 错误的写法,c 中不能像 oc 一样直接字符串赋值,要使用 strcopy
// Book1.subject = "C Programming Tutorial";
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
return 0;
}
# 结构作为函数参数
#include <stdio.h>
#include <string.h>
// 结构体声明必需在使用函数之前完成
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函数声明:函数实现在后面,但是必需提前声明函数方法 */
void printBook( struct Books book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* 输出 Book1 信息,传入结构体变量 */
printBook( Book1 );
return 0;
}
// 函数实现,结构体作参数,传入的是结构体变量名,而不是指针
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
# 共用体
在介绍共用体结构之前,先回顾一下前面介绍的数据类型:
1、int 整形
2、数组:一纬度或者多维,但是存储的内容是单一的。字符串是\0结尾的一纬数组
3、结构体:可以存储不同类型的变量值,每个变量都可以初始化。
4、指针:一种特殊的结构,指向地址的变量
5、共用体:可以定义不同类型的成员变量,但是只能有一个变量初始化,这样可以达到节省内存的作用,OC里的指针isa就是共用体结构
代码举例:
#include <stdio.h>
#include <string.h>
// 1、声明一个共用体Data,包含一个整形,浮点型,和字符串
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
// 2、共用体占用的总内存大小: 以成员变量最大的为准。这样保证都能初始化
printf( "Memory size occupied by data : %d\n", sizeof(data));
// 3、所有变量赋值完成后立即打印结果
data.i = 10;
printf( "data.i : %d\n", data.i);
data.f = 220.5;
printf( "data.f : %f\n", data.f);
strcpy( data.str, "C Programming");
printf( "data.str : %s\n", data.str);
// 4、全部打印一遍呢?
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
return 0;
}
结果:
// 立即打印
data.i : 10
data.f : 220.500000
data.str : C Programming
// 最后全部打印
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming
可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置
这样印证共用体只有最后一次赋值的变量能正确取值
# 位域结构
前面介绍了共用体结构,目的是节省内存,声明了多个成员变量,但是同时就会有一个有值。
那么位域是什么结构呢?
// 1、比如结构体声明如下:
struct STATUS
{
unsigned int tag1; // 值是0 或者 1
unsigned int tag2; // 值小于 3
unsigned int tag3; // 值小于 8
unsigned int tag4; // 值是0 或者 1
} sta1;
那么4个变量占用4*4=16字节 = 16*8 = 128位
// 2、但是实际上只用到了4位有效位数,针对这种场景可以进行进一步优化,就是位域结构:
struct STATUS
{
// 位域声明: type + 变量名 + 冒号 + 占用位数
unsigned int tag1 : 1; // 占用1位(值是0 或者 1)
unsigned int tag2 : 2; // 占用2位(值小于 3)
unsigned int tag3 : 3; // 占用3位(值小于 8)
unsigned int tag4 : 1; // 占用1位(值是0 或者 1)
} sta2;
那么这4个变量一共占用 1+2+3+1 = 7位,也就是一个字节就够用了,但是实际分配就是一个int的占用,也就是4字节。
// 3、位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的
struct STATUS
{
unsigned int tag1 : 1;
unsigned int : 7; // 占用7位,不使用
unsigned int tag3 : 3;
unsigned int tag4 : 1;
} sta3;
// 4、位域声明最大长度不能超过类型最大位数,并且赋值的时候,也不能超过最大范围。
struct STATUS
{
unsigned int tag1 : 33; // ❌ 33 位就是超过最大范围了,int最大32位。
unsigned int tag2 : 2;
} sta4;
赋值:
sta4.tag2 = 100; //❌错误,超过最大值了。2位只能赋值0,1,2,3 4种情况
位域的使用和结构成员的使用相同,其一般形式为:
位域变量名.位域名
位域变量名->位域名
用例:
#include <stdio.h>
int main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */
pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}
# 作用域
全局变量与局部变量在内存中的区别:
- 全局变量保存在内存的全局存储区中,占用静态的存储单元;
- 局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
局部变量优先级 > 全局变量
#include <stdio.h>
/* 全局变量声明: 作用域整个文件 */
int a = 20;
int main ()
{
/* 在主函数中的局部变量声明:作用域函数内 */
int a = 10;
int b = 20;
int c = 0;
int sum(int, int);
printf ("value of a in main() = %d\n", a);
c = sum( a, b);
printf ("value of c in main() = %d\n", c);
return 0;
}
/* 形式参数啊啊a,b :形式参数作用域在函数内,优先级高于全局变量 */
int sum(int a, int b)
{
printf ("value of a in sum() = %d\n", a);
printf ("value of b in sum() = %d\n", b);
return a + b;
}
编译: 从文件开头依次遍历,所以声明在前面的函数拿不到后面的函数声明实现
⚠️ 在上面的例子中,除了全局变量和局部变量作用域区别之外,还有函数的作用域,
main 函数在调用后面声明的sum 函数之前,必须要先声明一下sum函数方法: int sum(int, int);
否则在编译器会找不到sum 函数声明报错,如果直接将函数声明定义放在main函数之前不用。
⚠️ 那么结构体也可以声明在main函数之后,然后在mian函数内部使用吗?
不可以!结构体必需在调用函数使用之前声明,不能滞后。
问? 为什么两者有区别?