目录
一、字符串
1、字符串声明
C中没有字符串数据类型,字符串用字符数组或者字符指针表示,用空字符\0表示字符串的结尾,字符串数组需要存储字符串中的字符加上最后的空字符,注意字符串本身也是一个字符指针。字符串属于静态存储类型,相同类型的字符串只保存一份且不可修改,随应用程序加载加载到内存中,指向该字符串的所有字符指针保存的地址都一样,因为静态存储区的字符串不可修改所以不能通过字符指针修改字符串内容。字符数组声明的字符串在数组初始化时会将静态存储区保存的字符串拷贝一份放到数组中,因为数组中保存的是拷贝值,所以可以修改字符串的内容。
#include <stdio.h>
int main(void)
{
//C中字符串就是一个字符数组,数组的最后一个元素时字符'\0',标记字符串的结束
//如果字符数组的长度不够容纳字符串则字符串中多余的部分会被自动丢弃
char a2[2]="space farers";
//如果字符数组的长度超过字符串的长度加1,则多余的元素自动被初始化成'\0'
char a3[20]="space farers";
//通常使用不带数组长度的方式声明数组,由编译器自动在字符串后面加上'\0'并确定字符数组的长度
char a[]="space farers";
//因为数组名本质是一个指向第一个字符元素的不可变指针,所以这里也可以用指针的方式声明
char *b="space farers";
//如果字符串之间被空格隔开,编译器会自动合并之间的空格
char *c="sp" "ace fa" "rers";
printf("a2=%s \n",a2);
printf("a3=%s \n",a3);
printf("a=%s \n",a);
printf("b=%s \n",b);
printf("c=%s \n",c);
//字符串本身相当于字符数组的指针,指向字符数组的第一个字符
//字符串存储在静态存储区,相同内容的字符串只有一份且不可修改,所有指向该字符串的字符指针保存的地址都一样
//采用字符数组声明的字符串与用字符指针声明的字符串不同,编译器为字符数组分配完内存后,会把字符串从静态存储区拷贝至数组内
//然后将数组第一个元素的地址保存到数组名指针中,因此数组名指针与字符指针的地址不同
printf("%p, %p,%p, %c,%p\n", b, "space farers",c,*"space farers",a);
//因为字符指针指向的字符串是不可修改的,如果修改将影响所有指向该字符串的指针,通过指针修改字符串会报错Segmentation fault
// b[0]='c';
//字符数组因为数组保存的是静态存储区的副本,所以可以修改字符串
a[0]='c';
printf("modifed a=%s,b=%s\n",a,b);
return 0;
}
2、字符串数组声明
可以采用字符指针数组或者字符二维数组的形式声明字符串数组,字符指针数组保存的是字符指针,不能修改字符串内存,占用内存小;字符二维数组保存的是静态存储区的拷贝值,并且第二维数组的长度要大于字符串的最大长度,占用内存大,可以修改字符串。
#define LIM 5
int main(void)
{
//字符指针数组方式声明字符串数组,只存储指针,内存占用低,但是字符串内容不可修改
const char *mytalents[LIM] = {
"Adding numbers swiftly",
"Multiplying accurately", "Stashing data",
"Following instructions to the letter",
"Understanding the C language"
};
//字符二维数组方式声明字符串,因为保存了静态存储区的字符串的副本且第二维的长度需要大于最长字符串的长度
//所以此方式浪费内存,但是数组内容可以修改
char yourtalents[LIM][SLEN] = {
"Walking in a straight line",
"Sleeping", "Watching television",
"Mailing letters", "Reading email"
};
int i;
puts("Let's compare talents.");
printf ("%-36s %-25s\n", "My Talents", "Your Talents");
for (i = 0; i < LIM; i++)
printf("%-36s %-25s\n", mytalents[i], yourtalents[i]);
printf("\nsizeof mytalents: %zd, sizeof yourtalents: %zd\n",
sizeof(mytalents), sizeof(yourtalents));
return 0;
}
3、字符串常用函数
主要是字符串的输入/输出,拼接,比较,拷贝及转换成数字。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define STLEN 10
int main(void)
{
//推荐使用字符数组而非字符指针的方式,因为字符数组会提前分配足够容纳待读取字符串的内存
//而字符指针不会,从字符指针指定的内存位置写入读取的字符串会因为写入未分配内存而报错
char words[STLEN];
//puts函数向标准输出流中写入字符串,会自动添加换行符
puts("Enter strings (empty line to quit):");
//fgets函数返回读取的字符串的指针,如果读取失败返回NULL空指针
//fgets函数从标准输入流中读取读取字符串,读取最大长度为STLEN-1,stdin也可被替换成文件流,注意fgets函数会保存换行符
while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n')
//把读取的字符串写入到标准输出流,也可以是文件流,注意fputs函数不会自动添加换行符
fputs(words, stdout);
puts("Done.");
//字符指针形式的字符串不可修改,使用strcat报错Segmentation fault
// char *a="test";
char a[10]="test";
char *b="abcd";
//获取字符串长度
printf("a length:%d\n",strlen(a));
//字符串连接,把b加入a的后面,返回a,要求a是可修改的,且能够足够容纳b,如果a的空间不足报错
// char *c=strcat(a,b);
//同strcat,最后一个参数指定拼接到a的字符串的最大长度,达到该长度或者遇到空字符则终止,同样需要确保a有足够的空间
char *c=strncat(a,b,2);
printf("c:%s,%p,a:%p\n",c,c,a);
//比较两个字符串内容是否相同,会从第一个字符开始比较,直到第一个不同的字符,两者相同返回0,不同时返回值取决于实现
int dif=strcmp(a,b);
//同strcmp,最后一个参数限定比较的字符串长度,即只比较前面2个字符
// int dif=strncmp(a,b,2);
printf("str dif:%d\n",dif);
//把字符指针b指向的字符串拷贝至a+5指向的内存位置,需要确保a+5往后的内存有足够的内存空间,返回a+5的地址
// char *d=strcpy(a+5,b);
//同strcpy,最后一个参数限制拷贝的字符串的最大长度
char *d=strncpy(a+5,b,2);
printf("d:%s,%p,a+5:%p,a=%p,%s\n",d,d,a+5,a,a);
//将字符串转换成int数字,类似的有atol转换成long,atof转换成float
int e=atoi("234");
printf("e:%d\n",e);
return 0;
}
4、读取命令行参数
通过main函数的参数读取命令行参数,main函数可以没有参数,如果有参数只能是int argc或者char *argv[],参数名不能改,argc表示命令行中字符串的个数,其中第一个通常是执行的命令,后面的是命令参数,argv是保存命令行字符串的指针。
#include <stdio.h>
//第一个参数argc表示命令行中字符串的个数,第一个字符串通常是启动命令,后面的是启动参数
//第二个参数argv表示存储命令行字符串的数组
//int main(int argc, char *argv[])
int main(int argc)
{
int count;
printf("The command line has %d arguments:\n", argc - 1);
// for (count = 1; count < argc; count++)
// printf("%d: %s\n", count, argv[count]);
// printf("\n");
return 0;
}
二、变量属性
1、左值与右值
如int a=3,系统为a这个变量标识符分配一块内存,并把内存中的数据初始化为3,这块内存称为“对象”,a指定了这个对象,称之为左值,可以通过a修改“对象”的值,如a=5,则称a为可修改的左值;3或者5这类常量表达式,不指定任何对象,称为右值。如char *a="test", 这里a是左值,a+1不是左值,*(a+1)是左值,即左值可以放到等号的左边赋值,右值是给左值赋值的表达式。
2、作用域
作用域是变量标识符的属性,是指程序中可以访问这个标识符的区域,分为块作用域,函数作用域,函数原型作用域和文件作用域。
- 块作用域:块是一对花括号括起来的语句,比如函数体就是一个块,for循环或者if语句可以省略花括号,也是一个块,块作用域的范围是从块的定义处开始到块的末尾,如函数的参数,函数中定义的变量,for循环定义的变量都具有块作用域。
- 函数作用域:仅用于goto标签,函数作用域的范围是整个函数,即使该标签首次出现在函数的内层块中,可避免同一个函数的不同块中出现两个相同的标签。
- 函数原型作用域:用于函数原型声明中的形参名,函数原型作用域的范围是指从形参定义处开始到函数原型声明结束,因此,函数原型声明中的形参名可以与函数定义中的形参名不一致,且除变长数组外,其他类型变量的形参都可以省略形参名。
- 文件作用域:用于定义在函数外面的变量,函数作用域的范围是指从该变量的定义处开始到文件结尾处。因为函数作用域变量可以被多个函数访问,所以也称为全局变量。因为通过#include预处理指令引入头文件实际是将头文件的文本内容与源代码文件合并形成一个新的文件,称之为翻译单元,头文件中定义的文件作用域变量对该翻译单元都可见
注意同一作用域范围内不能声明相同标识符的变量,不同作用域同一标识符的变量,内层作用域会自动覆盖掉外层作用域的定义,如下用例:
#include <stdio.h>
int x=1;
void test(int x);
int main()
{
printf("x in outer method: %d at %p\n", x, &x);
//覆盖了外层x的定义
// int x = 30;
//可以使用auto关键字显式覆盖外层定义,表明该变量是一个块作用域变量
auto int x = 30;
printf("x in outer block: %d at %p\n", x, &x);
{
//覆盖外层x的定义
int x = 77;
printf("x in inner block: %d at %p\n", x, &x);
}
printf("x in outer block: %d at %p\n", x, &x);
while (x++ < 33)
{
//覆盖外层x的定义
int x = 100;
x++;
printf("x in while loop: %d at %p\n", x, &x);
}
printf("x in outer block: %d at %p\n", x, &x);
test(10);
return 0;
}
void test(int x){
printf("x in method param:%d,at %p\n",x,&x);
//不能重复声明
// int x=2;
{
//覆盖外层x的定义
int x=3;
printf("x in test method:%d,at %p\n",x,&x);
}
}
3、存储期
作用域和链接描述了标识符的可见性,存储期描述了标识符对应的对象的生存期,包括静态存储期,线程存储期,自动存储期和动态分配存储期。
静态存储期:所有的文件作用域变量和static修饰的块作用域变量都具有静态存储期,这类变量保存在可执行文件中.data段内,可执行文件加载到内存中时自动加载到内存,只初始化一次,如果代码未初始化则编译器会默认将其初始化为0,在程序的执行期间一直存在。
线程存储期: 以关键字_Thread_local修饰的具有文件作用域的变量都具有线程存储期,该关键字表示每个线程都会获得该变量的副本,从被声明时到线程结束变量一直存在。
自动存储期:通常块作用域变量都具有自动存储期,这类变量是当CPU执行到变量初始化的代码时,通过汇编指令mov自动为变量分配内存并初始化,当CPU执行完该变量所在的方法A时栈顶指针恢复到调用A的方法B时,该变量对应的内存会在执行B方法时分配给其他变量,相当于已经释放掉了。
动态分配存储期:通过malloc函数分配内存的变量具有动态内存分配存储期,从malloc分配内存开始到free函数释放该变量对应内存期间变量一直存在。
静态存储期的测试用例参考:
#include <stdio.h>
int stay2;
void trystat(void);
int main(void)
{
int count;
for (count = 1; count <= 3; count++)
{
printf("Here comes iteration %d:\n", count);
trystat();
}
return 0;
}
void trystat(void)
{
//fade和fade2都是自动存储期变量,每次执行trystat方法都会重新分配内存并初始化
int fade = 1;
//fade2并未初始化,使用的是分配给fade2的内存上的值
int fade2;
//stay,stay2,stay3三个都具有静态存储期,三个都是只在程序加载期间初始化一次,内存位置在编译成可执行文件时已确定
static int stay = 1;
//编译器会自动初始化未初始化的静态存储期变量为0
static int stay3;
printf("fade = %d fade2 = %d stay = %d, stay2 =%d, stay3=%d \n", fade++,fade2,stay++,stay2++,stay3++);
}
4、链接
C变量有3种链接属性,外部链接、内部链接和无链接,具有块作用域、函数作用域和函数原型作用域的变量都是无链接属性,即这些变量属于定义变量的块、函数或者函数原型私有。多个源代码文件一起编译时,其中一个源代码文件定义的变量可以被一起编译的其他源代码文件访问,则称该变量具有外部链接属性,具有文件作用域的变量默认具有外部链接属性,在其他文件使用该变量时需要使用extern关键字声明该变量。如果文件作用域变量被static关键字修饰,则该变量只能在定义该变量的源代码文件对应的翻译单元内使用,属于该翻译单元私有,不能被其他源代码文件访问,则称该变量具有内部链接属性。
//parta.c文件
#include <stdio.h>
void report_count();
void accumulate(int k);
//文件作用域,静态存储期,外部链接
int count = 0;
int main(void)
{
//自动存储期,块作用域,无链接变量
int value;
//寄存器变量,块作用域,无链接
register int i;
printf("Enter a positive integer (0 to quit): ");
while (scanf("%d", &value) == 1 && value > 0)
{
++count; // use file scope variable
for (i = value; i >= 0; i--)
accumulate(i);
printf("Enter a positive integer (0 to quit): ");
}
report_count();
return 0;
}
void report_count()
{
//该声明是可选的,表示该变量的定义在本函数外
// extern int count;
printf("Loop executed %d times\n", count);
}
//partb.c文件,与parta.c文件一起编译
#include <stdio.h>
//表示count是其他文件定义的,编译器会自动查找定义该变量的文件
extern int count;
//文件作用域,静态存储期,内部链接,不能被其他文件访问
static int total = 0;
void accumulate(int k);
void accumulate(int k)
{
//具有块作用域,静态存储期的变量
static int subtotal = 0;
if (k <= 0)
{
//subtotal和total的值的变化一样
printf("loop cycle: %d\n", count);
printf("subtotal: %d; total: %d\n", subtotal, total);
}
else
{
subtotal += k;
total += k;
}
}
5、auto关键字
显示声明该变量是一个自动存储期变量,如果外层作用域存在同名变量则覆盖同名变量的定义
6、static关键字
修饰块作用域变量时表示该变量具有静态存储属性;修饰文件作用域变量时表示该变量具有内部链接属性;修饰函数时表示该函数是静态函数,只能在函数定义的文件内使用,即私有函数,默认都是外部函数,可以被其他文件访问,其他文件访问时需要使用extern关键字声明。
7、extern关键字
修饰变量时表示该变量的定义在函数外部或者其他源代码文件中,通常用于修饰具有文件作用域的全局变量,表示该变量是其他文件定义的,注意此时不需要对该变量做初始化,编译器也不会对该变量重新分配内存。修饰函数时表示该函数的定义在其他文件中。
8、register关键字
register关键字可以修饰块作用域变量,该关键字向编译器暗示尽可能使用寄存器保存该变量,最终结果由编译器根据变量的数据类型和可用的寄存器数量决定。
三、结构变量
C中的结构类似Java中的对象,可以包含各种字段属性和方法指针。
1、声明和赋值
注意赋值时编译器按照声明字段的顺序对字段逐一赋值,编译器不检查赋值的数据类型是否符合要求,如果不符合数据类型要求或者赋值个数超过定义的字段数编译不报错,但是赋值失败;未赋值的字段,即未初始化的字段,编译器会自动初始化为0或者空。
#include <stdio.h>
#define MAXTITL 41 /* maximum length of title + 1 */
#define MAXAUTL 31 /* maximum length of author's name + 1 */
//结构体声明,描述一个结构的各个字段属性,其中book称为结构标记,两个{}的部分叫结构模板
struct book {
//注意此处不推荐使用字符指针,使用字符指针时如果往该地址写入数据,因为指针指向的地址未初始化
//或者不可修改会报错,如果必须使用字符指针,字符指针应该指向有malloc函数预先分配的一个内存地址
//char* title;
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
//此book2是一个变量名,相当于声明了一个匿名的结构体,无法复用结构模板声明新的变量
struct {
char title[MAXTITL];
char author[MAXAUTL];
float value;
} book2;
int a=1.2;
struct book b2={
"ab",
"ac",
//编译报错,同其他具有静态存储期的普通变量,结构变量初始化时必须使用常量或者常量表达式,不能使用变量
// a
1.2
};
int main(void)
{
//声明结构变量
struct book library;
//声明指向结构体book的指针
struct book * pt;
struct book b3={
"ab",
"ac",
//具有自动存储期的变量,可以使用其他变量初始化
a
};
//结构变量通过.访问结构成员
printf("b3-->%s,%s,%f\n",b3.title,b3.author,b3.value);
//初始化结构指针,结构指针通过->访问字段成员
pt=&b3;
printf("pt-->%s,%s,%f\n",pt->title,pt->author,pt->value);
//用结构体变量对另一个结构体变量赋值
library=b3;
printf("library-->%s,%s,%f\n",library.title,library.author,library.value);
//编译器按照顺序对字段逐一赋值,且不会检查变量类型是否符合字段要求,如果不符合则赋值失败,但不报错,未初始化的字段自动为0或者空
struct book b4={1,2};
printf("b4-->%s,%s,%f\n",b4.title,b4.author,b4.value);
//指定初始化器,.value是结构体最后一个元素,.value后面的两个元素都是多余的,编译会有警告
struct book b5={
.value=1.3,
"ac",
a
};
printf("b5-->%s,%s,%f\n",b5.title,b5.author,b5.value);
//a对.author后面一个字段value赋值,即value赋值了两次,以最后一次为准
struct book b6={
.value=1.3,
.author="ac",
a
};
printf("b6-->%s,%s,%f\n",b6.title,b6.author,b6.value);
return 0;
}
2、结构数组
如下,把struct book这个整体当做一个新的数据类型去处理即可
#define MAXTITL 41
#define MAXAUTL 31
struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
int main(void)
{
struct book booka={"ab3","cd3",1.3};
//结构体数组的声明和初始化
struct book library[3]={
{"ab","cd",1.1},
{"ab2","cd2",1.2},
booka
};
//因为数组不能二次赋值,所以只能对数组元素赋值
// library[0].title="test";
library[0].title[2]='e';
library[0].value=1.8;
printf("char size:%d,float size:%d\n",sizeof(char),sizeof(float));
//结构体的内存大小等同于各字段的内存大小相加,结构体数组的大小等于结构体的大小乘以元素个数
printf("struct book size:%d, library size:%d\n",sizeof(struct book),sizeof library);
//访问结构体数组成员字段属性
printf("book1 title:%s, author:%s, value:%f \n",library[0].title,library[0].author,library[0].value);
return 0;
}
3、结构嵌套
将结构本身视为一种新的数据类型,允许嵌套多层
#include <stdio.h>
#define LEN 20
struct names {
char first[LEN];
char last[LEN];
};
struct guy {
//结构嵌套时,struct names相当于一种新的数据类型
// struct names handle;
//声明匿名结构的两种方式
struct{
char first[LEN];
char last[LEN];
} handle;
//编译器自动将匿名结构的字段合并到外层结构中
struct{
char first2[LEN];
char last2[LEN];
};
char favfood[LEN];
char job[LEN];
float income;
};
int main(void)
{
struct guy fellow = {
{ "Ewen", "Villard" },
{ "Ewen2", "Villard2" },
"grilled salmon",
"personality coach",
68112.00
};
//访问嵌套结构的属性
printf("first name:%s,first2 name:%s\n",fellow.handle.first,fellow.first2);
//分别是40 和 124,float占4字节,char占1字节
printf("name size:%d, guy size:%d \n",sizeof(struct names),sizeof(struct guy));
return 0;
}
4、结构变量如何传递给函数
结构变量本身可以作为参数传递给函数,也可以作为函数的返回值,作为参数传递时程序会自动复制结构变量的所有字段属性,如果结构复杂,则复制耗时长且耗内存,优点时函数不会修改原结构变量的字段数据。推荐使用结构指针的方式,指针本身占用内存小,复制快,缺点是函数会修改结构变量中的字段数据。
#include <stdio.h>
#include <string.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds *);
double sum2(struct funds moolah);
void change(char *str,char str2[]);
int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
4032.27,
"Lucky's Savings and Loan",
8543.94
};
//传递结构指针, 指针占用内存更小,复制更快,但是结构的字段数据可能有被修改的问题,如果不想被修改,可以加上const关键字
printf("Stan has a total of $%.2f.\n", sum(&stan));
printf("Stan bank-->%s\n",stan.bank);
//传递结构本身,编译器会自动复制当前结构变量的所有字段,如果结构复杂,复制会很耗时且占用内存,但是不用担心函数修改字段属性的问题
printf("Stan2 has a total of $%.2f.\n", sum2(stan));
printf("Stan2 bank-->%s\n",stan.bank);
return 0;
}
double sum(const struct funds * money)
{
change(money->bank,"test 123");
return(money->bankfund + money->savefund);
}
//传递结构本身,因为结构本身是一个复杂数据类型,早期C语言的实现不允许使用结构作为形参,现在都已支持
double sum2(struct funds moolah)
{
change(moolah.bank,"test 345");
return(moolah.bankfund + moolah.savefund);
}
void change(char *str,char str2[]){
int len=strlen(str2);
for(int i=0;i<len;i++){
*(str+i)=str2[i];
}
}
5、伸缩型数组成员
伸缩型数组成员就是长度不确定的数组成员,数组长度在运行时确定,编译器不会预先给该成员分配内存,通过malloc函数在运行时分配内存。
#include <stdio.h>
#include <stdlib.h>
struct flex
{
size_t count;
double average;
//scores是伸缩型数组成员,即运行时确定数组的长度,编译器不会预先为该属性分配内存
//注意伸缩型数组成员必须是结构中最后一个字段,且结构中至少有一个成员
double scores[];
};
void showFlex(const struct flex * p);
int main(void)
{
struct flex * pf1, *pf2;
int n = 5;
int i;
int tot = 0;
//未给scores分配内存
printf("flex size:%d,size_t size:%d, double size:%d\n",sizeof(struct flex),sizeof(size_t),sizeof(double));
//报错,对可变数组成员非静态地初始化
// struct flex a={1,1.2,{1.2}};
struct flex a={1,1.2};
printf("flex size_t:%d,average:%f\n",a.count,a.average);
//通过malloc函数为scores字段动态分配内存
pf1 = malloc(sizeof(struct flex) + n * sizeof(double));
pf1->count = n;
for (i = 0; i < n; i++)
{
pf1->scores[i] = 20.0 - i;
tot += pf1->scores[i];
}
pf1->average = tot / n;
showFlex(pf1);
//注意不要使用这种整体赋值的方式,这时不会复制伸缩性结构变量,因为不知道该变量的实际大小
//如果必须复制,需要使用memcpy函数,直接复制内存
// *pf1=*pf2;
n = 9;
tot = 0;
pf2 = malloc(sizeof(struct flex) + n * sizeof(double));
pf2->count = n;
for (i = 0; i < n; i++)
{
pf2->scores[i] = 20.0 - i/2.0;
tot += pf2->scores[i];
}
pf2->average = tot / n;
showFlex(pf2);
free(pf1);
free(pf2);
return 0;
}
void showFlex(const struct flex * p)
{
int i;
printf("Scores : ");
for (i = 0; i < p->count; i++)
printf("%g ", p->scores[i]);
printf("\nAverage: %g\n", p->average);
}
四、联合union
联合用于在一块内存中存储不同数据类型,编译器按照内存占用最大的字段分配内存,保证内存按照内存占用最大的数据类型对齐。联合各字段的内存地址一样,对其中一个字段赋值,会把之前对另一个字段的赋值擦掉。
1、联合的声明和赋值
#include <stdio.h>
//声明一个联合,联合用于在一块内存中存储不同数据类型,编译器按照内存占用最大的字段分配内存,保证内存按照内存占用最大的数据类型对齐
union test {
double a;
int b;
char c;
char d[10];
};
//按int对齐,大小为8
union test2 {
int b;
char c;
char d[6];
};
//按int对齐,大小为4
union test3 {
int b;
char c;
char d[2];
};
//按double对齐,大小为24
union test4 {
double b;
char c;
char d[17];
};
int main(void)
{
printf("double size:%d,char size:%d,int size:%d,test size:%d\n",
sizeof(double),sizeof(char),sizeof(int),sizeof(union test));
printf("test2 size:%d\n",sizeof(union test2));
printf("test3 size:%d\n",sizeof(union test3));
printf("test4 size:%d\n",sizeof(union test4));
union test a={1.2};
//初始化同struct,按照字段声明的顺序初始化
// union test b={"sds"};
//一次只能初始化一个字段,多余的被编译器自动丢弃
// union test b={1.2,1};
//支持指定初始化器
union test b={.c='a'};
//同struct可整体赋值
union test b2=b;
union test *b3=&b;
union test b4={.d="test"};
//未初始化字段读取的值不可控
printf("test b:%d\n",b.b);
printf("test c:%c\n",b3->c);
printf("test d:%s\n",b4.d);
//同一个联合对另一个字段赋值时,原来已赋值的字段自动擦除
b.a=1.2;
printf("test c:%c,a:%f\n",b.c,b.a);
//两个字段的内存地址相同
printf("test c:%p,a:%p\n",&b.c,&b.a);
return 0;
}
2、匿名联合
匿名联合通常和结构配合使用,通过结构的一个属性判断匿名联合中使用什么字段
#include <stdio.h>
struct author{
char name[10];
int age;
};
struct author2{
char name[20];
long age;
};
struct book{
char name[10];
int value;
//匿名联合,可以通过value判断使用哪个author
union{
struct author author;
struct author2 author2;
};
};
void printfBook(struct book *b);
int main(void)
{
struct book b={"test",1,{.author={"abc",12}}};
struct book b2={"test",2,{.author2={"def",34}}};
printfBook(&b);
printfBook(&b2);
return 0;
}
void printfBook(struct book *b){
if(b->value==1){
printf("author name:%s,age-->%d\n",b->author.name,b->author.age);
}else{
printf("author2 name:%s,age-->%d\n",b->author2.name,b->author2.age);
}
}
五、枚举enum
用字符串(枚举符)代替数字表示某个变量的有限的几种取值,相比Java枚举要简单很多
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
char * s_gets(char * st, int n);
//用enum关键字创建一个新的枚举类型spectrum,后面的red称为枚举常量,枚举常量实际是int类型,默认从0开始,0,1,2依次
enum spectrum {red, orange, yellow, green, blue, violet};
//也可对枚举常量单独赋值,未赋值的枚举常量在前一个已赋值常量基础上累加
//blue和violet跟spectrum的定义重复,编译报错
//enum spectrum2 {red2=20, orange2=10, yellow2, green2, blue, violet};
enum spectrum2 {red2=20, orange2=10, yellow2, green2, blue2, violet2};
const char * colors[] = {"red", "orange", "yellow",
"green", "blue", "violet"};
#define LEN 30
int main(void)
{
char choice[LEN];
enum spectrum color;
bool color_is_found = false;
printf("spectrum red=%d,orange=%d,yellow=%d,green=%d \n",red,orange,yellow,green);
printf("spectrum2 red2=%d,orange2=%d,yellow2=%d,green2=%d \n",red2,orange2,yellow2,green2);
puts("Enter a color (empty line to quit):");
while (s_gets(choice, LEN) != NULL && choice[0] != '\0')
{
for (color = red; color <= violet; color++)
{
//比较两个字符串是否一样
if (strcmp(choice, colors[color]) == 0)
{
color_is_found = true;
break;
}
}
if (color_is_found)
//遍历枚举
switch(color)
{
case red : puts("Roses are red.");
break;
case orange : puts("Poppies are orange.");
break;
case yellow : puts("Sunflowers are yellow.");
break;
case green : puts("Grass is green.");
break;
case blue : puts("Bluebells are blue.");
break;
case violet : puts("Violets are violet.");
break;
}
else
printf("I don't know about the color %s.\n", choice);
color_is_found = false;
puts("Next color, please (empty line to quit):");
}
puts("Goodbye!");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
//从标准输入中读取最多n个字符,放入st指向的字符串
ret_val = fgets(st, n, stdin);
if (ret_val)
{
//查找st中的换行符,返回第一次出现的内存地址
find = strchr(st, '\n');
//如果找到了
if (find)
//将其替换为换行符
*find = '\0';
else
//如果没有找到,说明有未读取完的字符,用getChar读取完剩余的字符
while (getchar() != '\n')
continue;
}
return ret_val;
}
六、typedef关键字
typedef关键字用于把某个数据类型重命名成一个新的名称,与#define相比,typedef创建的符号只能用于数据类型,不能用于值,typedef由编译器解释,#define由预处理器解释。
#include <stdio.h>
//两种方式是等价的
#define LONG long
typedef long LONG2;
//两种不等价
#define STRING char *;
typedef char * STRING2;
//两种不等价
typedef struct{int age;char name[10];} PER;
//#define PER2 struct{int age;char name[10];};
int main(void)
{
LONG a=1;
LONG2 a2=1;
//就报错c为解析
// STRING b,c;
STRING2 b,c;
PER a={13,"a"};
//使用报错,编译器无法识别{}的类型
// PER2 b={13,"a"};
return 0;
}