【C语言学习笔记】翁恺课程(5)

目录

10.  字符串

字符串变量

字符串输入输出

字符串数组

单字符的输入输出

strlen

strcmp

 strcpy

strcat

字符串中找字符strchr

11.  枚举、结构类型、联合

11.1  枚举

11.2  结构

声明结构类型

在函数内外?

结构变量

结构成员

结构运算

结构指针

结构与函数

结构中的结构

自定义数据类型(typedef)

 11.3 联合


10.  字符串

字符数组
char word[] = {'H','e','I','I','o','!',};

word[0]H
word[1]e
word[2]l
word[3]l
word[4]o
word[5]!

这不是C语言的字符串,因为不能用字符串的方式做计算 。

char word[] = {'H','e','I','I','o','!','\0'};

word[0]H
word[1]e
word[2]l
word[3]l
word[4]o
word[5]!

word[6]

\0

如此为C语言的字符串:最后有个0或者\0

——  以0(整数O)结尾的一串字符:0或'\0'是一样的,但是和'0'不同
——  0标志字符串的结束,但它不是字符串的一部分。计算字符串长度的时候不包含这个0
——  字符串以数组的形式存在,以数组或指针的形式访问:更多的是以指针的形式
——  string.h里有很多处理字符串的函数

字符串变量

——  char *str = "Hello";  ——(有一个指针叫str指向一个字符数组,里面放的内容是Hello)
——  char word[] =“Hello"; —— (有个字符数组,里面内容为Hello)
——  char line[10] =“Hello"; ——(有个字符数组叫line 有十个字节那么大,里面放了Hello,五个字符,这五个字符在line里占多大空间?——6个位置(加上结尾的0))

字符串常量

—— "Hello"    (双引号)
——  "Hello”会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
——  两个相邻的字符串常量会被自动连接起来

使程序看起来窄一点,适合写和阅读程序

C语言的字符串是以字符数组的形态存在的:不能用运算符对字符串做运算、通过数组的方式可以遍历字符串。

唯一特殊的地方是字符串字面量可以用来初始化字符数组。以及标准库提供了一系列字符串函数。

字符串变量

char* s = "Hello, world!";

#include <stdio.h>

int main(void)
{
	char *s = "Hello world";
	s [0] = 'B';//尝试把H替换为B,然后输出s[0]
	
	printf("Here!s [0]=%c\n", s[0]);
	
	return 0;
}

编译无问题,但是运行时没法输出s[0],出错了。
在print之前,程序就是出错。

#include <stdio.h>

int main(void)
{
	int i = 0;
	char *s = "Hello world";
	//s [0] = 'B';//尝试把H替换为B,然后输出s[0]
	char *s2 = "Hello world";
	
	printf("&i=%p\n",&i);
	printf("s =%p\n",s);
	printf("s2=%p\n",s2);

	printf("Here! s[0]=%c\n", s[0]);
	
	return 0;
}

结果发现:i的地址相对很大,而s和s1的地址相对很小。s和s1的指向了同一个地址(程序的代码段,只读)。

字符串常量

char* s = "Hello, world!";
s是个指针,初始化为指向一个字符串常量
        由于这个常量所在的地方,实际上 s 是const char* s,但是由于历史原因,编译器接受不带const的写法。
        但是试图对s所指向的字符串做写入会导致严重的后果。
如果需要修改字符串,应该用数组
char s[] = "Hello World!";

 用数组的s3也在一个很大的地方,且它的0可以修改。s,s2是指针,他们指向实际存放Hello world的地方很远,不可写。

指针还是数组?
char *str = "Hello";
char word[] = "Hello";
—— 数组:这个字符串在这里
        作为本地变量空间自动被回收
—— 指针:这个字符串不知道在哪里
        处理参数
        动态分配空间

如果要构造一个字符串——>数组
如果要处理一个字符串——>指针

char* 是字符串?
字符串介意表达为char*形式;
char* 不一定是字符串:
        本意是指向字符的指针,可能指向的是字符的数组也可能指向单个数组(就像int*一样,int*可能指向单个int,也可能指向一个int的数组)
        只有它所指的字符数组有结尾的0时,才能说它所指的是字符串。

字符串输入输出

字符串赋值

- char *t ="title";
- char *s;
- s = t;
- 并没有产生新的字符串,只是让指针s指向了t所指的字符串对s的任何操作就是对t做的。

字符串输入输出

- char string[8];
scanf("%s" ,string);
printf("%s" , string);

- scanf读入一个单词(到空格、tab或回车为止)
- scanf是不安全的,因为不知道要读入的内容的长度。

char str1[8],str2[8];
scanf("%s",str1);
scanf("%s",str2);

输入12345678 12345678,结果第一个字符串为空,第二个字符串却是12345678读了八个字符。(运气好没有崩溃)
原因:涉及到这些变量在内存中是怎么排列的存放的。

把要做的事放到函数里:

 数组越界了,只有8个char可以放,两次输入都超过了8个,导致程序崩溃。

在%和s中间加个数字,表示最多读几个字符。

最多7个,则123全部读入,而第二个scanf读入12345678中的7个,1234567
第一个scanf就超过了7个,使得前面7个给了第一个scanf,剩下的8给了第二个scanf,正好满足了第二个scanf。

—— 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一。
—— 下一次scanf从哪里开始?

常见错误

——  char *string;
——  scanf("%s" , string);
——  以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
        —  由于没有对string初始化为0,所以不一定每次运行都出错

 空字符串

——  char buffer[100]="";
        这是一个空的字符串,buffer[0] == ‘\0'
——  char buffer[]= "";
        这个数组的长度只有1。只有buffer[0]有效,且buffer[0] == ‘\0',放不下任何字符。

字符串数组

 char **a:a是一个指针,指向另一个指针,哪个指针指向一个字符(串)。

char a[][]:a是一个二维数组,但第二维需要给具体的值,否则编译不通过。

char a[][10]={
    "hello",
    "world"
};//每个字符串长度不要超过n,即10

a是一个数组,a内的每一个单元是一个char[10]:即a[0]——>char [10]。

另一种写法:能编译通过:

 此时:a[0]——>char*

a是数组,数组里放的是指针,a[i]指向一个存放字符串的地址。而二维数组a[0]就是第一行的字符串。

之前的题目:输入数字月份,输出对应的月份英文单词。现在可以用字符串数组做。  

程序参数

—— int main(int argc, char const *argv[])
—— argv[0] 是命令本身
        当使用Unix的符号链接时,反映符号连接的名字。
argc告诉我们,后面的数组有多少个字符串。
然后我们试着输出后面字符串的内容

#include <stdio.h>

int main( int argc, char const *argv [])
{
	int i;
	for ( i=0; i<argc; i++ ) {
		printf ( "%d : %s\n", i, argv[i] );
	}
	return 0;
}

只是运行,输出当前目录下,/a.out 
带上123, 输出/a.out 和123.
它从第2个字符串开始记录所有你输入的字符串,而第一个参数,即argv[0],则是输入的这个文件的名字(./a.out)即可执行程序的名字。

在Unix还有更复杂的东西:

如果将a.out 称之为my,ln -s a.out my,然后我们看my,ls -l my发现my是个指向a.out 的链接。如果执行my  ./my 123 输出的字符串argv[0]也是./my而不是./my了。

我们需要在程序里知道,你是怎么运行程序的,通过哪种方式,直接运行?还是通过某种链接?也许可以根据这个做些其他事儿。
建议搜索busybox,看看别的box是怎么做的。

windows很少用命令行运行程序,快捷方式中也可指定给可执行程序的那些参数,因此也有意义。

单字符的输入输出

putchar

int putchar(int c);
向标准输出写一个字符;返回写了几个字符,EOF(-1)表示写失败。

getchar

int getchar(void);
从标准输入读入一个字符;返回类型是int,是为了返回EOF(-1):windows——>Ctrl+Z;Unix——>Ctrl+D;

#include <stdio.h>

int main( int argc, char const *argv [])
{
	int ch;
	while((ch=getchar())!=EOF){
		putchar(ch);
	}
	printf("EOF\n");//这样来看读入什么会EOF
	
	return 0;

}

不管怎么输入数字串、字符串都不停止,直到输入Ctrl-C,程序直接结束但没有看到EOF,这表示我们将程序强制结束了而不是正确的输入EOF。
第二次尝试输入Ctrl-D得到输出EOF(Windows要输入Ctrl-Z)。
而且另一件奇怪的事情是,即便输入12435435数字串,敲下回车之前都不会有回应,敲下回车后才原封不动地输出这一串。为什么?getchar()不是一个个读的吗?
原因:之前提到过中介shell,shell先处理输入的东西再给程序,输出的东西也先经过处理再呈现给我们。(当时在讲\b会变成什么样子)
用户(键盘等输入)→shell→程序
用户←shell←程序
shell对输入的东西做了行编辑,也就是敲下回车前输入的部分都在shell里处理,放在缓冲区里,按下回车后才送到程序那里。如:输入123,shell缓冲区里为“1,2,3,回车”。然后getchar()再读缓冲区。
如果按下Ctrl-D,shell会生成一个EOF的标志。而按下Ctrl-C,shell直接关闭了程序。
所以用户的输入是让shell填写缓冲区,而scanf()和getchar()都是在缓冲区内读。

字符串处理函数原型 头文件:#include <string.h>
常用的六个函数:strlen、strcmp、strcpy、strcat、strchr、strstr。

strlen

size_t strlen(const char *s);
返回s的字符串长度,(不包括结尾的0)

#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[]){
	char line[] = "Hello";
	printf("strlen = %lu\n",strlen(line));
	printf("sizeof = %lu\n",sizeof(line));
	
	return 0;
}

 结果:strlen = 5
            sizeof = 6(sizeof还有结尾的\0)

#include <stdio.h>
#include <string.h>

//试着自己写strlen函数
size_t mylen(const char* s){
	int index = 0;
	while(s[index] !='\0' ){
		index++;
	}
	return index;
}

int main(int argc, char const *argv[]){
	char line[] = "Hello";
	printf("strlen = %lu\n", strlen(line));
	printf("sizeof = %lu\n", sizeof(line));
	printf("mylen = %lu\n", mylen(line));
	
	return 0;
}

strcmp

int strcmp(const char *s1, const char *s2);
比较两个字符串,返回:
        0: s1==s2
        1: s1>s2.
        -1: s1<s2

试着用printf("%d\n",s1==s2);来判断s1和s2是否相等:永远false(返回0),比较的是s1和s2的地址

 当出现不相等的字符或者字符串到了末尾时,返回差值

 处理字符串的两种手段,数组和指针。
数组:用一个整数作为数组的下标,遍历字符串。
指针:反正函数拿到的参数是指针,指针自己在++,其所指的那个东西去做判断

第二种while在改变指针所指的地址,在移动指针,而不是在指针上加idx。
哪种效率好,编译器会优化,最后编译器产生的代码可能差不多。

 strcpy

——  char * strcpy(char *restrict dst, const char *restrict src);
(把第二个参数里头所表达的字符串拷贝到第一个参数所表达的那个空间里去。)
——  把src的字符串拷贝到dst
        restrict表明src和dst不重叠(C99)
——  返回dst
        为了能链起代码来

复制一个字符串

char *dst=(char*)malloc(strlen(src)+1);//不包含结尾的\0,所以+1
strcpy (dst,src);

记得 +1 

数组版本

//试着自己写strcpy函数
char *mycpy(char*dst,const char*src)
{
	int idx=0;
	while(src[idx]!='\0')
	{
		dst[idx]=src[idx];
		idx++;
	}
	dst[idx+1]='\0';
	return dst;
}

指针版本

char *mycpy(char* dst,const char* src)
{
	char *ret=dst;
	while(*src!='\0')
	{
		*dst=*src;
		*dst++;
		*src++;
	}
	*dst='\0';
	return ret;
}

优化

while(*src)*dst++ = *src++;

*dst++ = *src++;赋值运算表达式的结果就是*src

char *mycpy(char* dst,const char* src)
{
	char *ret=dst;
	while(*dst++ = *src++)
	{
		;
	}
	*dst='\0';
	return ret;
}

strcat

—— char * strcat(char *restrict s1 , const char *restrict s2);
——  把s2拷贝到s1的后面,接成一个长的字符串
—— 返回s1
—— s1必须具有足够的空间

s1结尾的\0将被s2的开头替换掉。

安全问题:strcpy和strcat都可能出现安全问题:如果目的地没有足够空间?

安全版本:strncpy和strncat

—  char * strncpy(char *restrict dst, const char *restrict src, size_t n);
—  char * strncat(char *restrict s1, const char *restrict s2, size_t n);
—  int strncmp(const char *s1, const char *s2, size_t n); 比较前n个字符。

字符串中找字符strchr

——  char * strchr(const char *s, int c); 
       —  在字符串中从左往右找c第一次出现的位置,返回指针。
——  char * strrchr(const char *s, int c);
       — 从右往左找
——  返回NULL表示没有找到
——  返回非NULL给的是指针,指向要找的字符。

如何寻找第2个?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[]){
	char s[] = "hello";
	char *p = strchr(s,'l');
	printf("%s\n",p);		// 结果为llo,返回指向l的指针,把l及后面的东西当字符串输出
	
//	//如何找第二个l?
//	p = strchr(p+1,'l');	// lo中找l
//	printf("%s\n",p);		// 结果为lo

	// 找到l把其后面的东西复制到另一个东西
	char *t = (char*)malloc(strlen(p)+1);
	strcpy(t,p);
	printf("%s\n",t);
	free(t);
	
	//找到l,想要l前面的一段
	char c =*p;		//找到l后,p为指向‘l’的指针,此步将l暂存到c中: = 'l'
	*p = '\0';		//将指针p指向的内容改为\0,则s为 h e \0 l o \0,即"he"
	char *t1 = (char*)malloc(strlen(s)+1);	// 请求内存
	strcpy(t1,s);		// 拷贝给t1
	printf("%s\n",t1);
	free(t1);		// 释放内存
	*p = c;			// 将p指向内容'\0'还原为'l'
	
	return 0;
}

字符串中找字符串:char* strchr(const char* s1,const char*s2);

不区分大小写:char* strcasestr(const char* s1,const char*s2);

11.  枚举、结构类型、联合

11.1  枚举

常量符号化

用符号而不是用具体数组来表示程序中的数字

枚举

用枚举而不是定义独立的const int变量

— 枚举是一种用户定义的数据类型,它用关键字enum以如下语法来声明︰
                enum 枚举类型名字{名字0,....名字n};
— 枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是就是常量符号它们的类型是int,值则依次从0到n。如:
                enum colors { red,yellow,green } ;
— 就创建了三个常量,red的值是0,yellow是1,而green是2。
— 当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字。

—— 枚举量可以作为值
—— 枚举类型可以跟上enum作为类型
—— 但是实际上是以整数来做内部计算和外部输入输出的

套路:自动计数的枚举

—— 这样需要遍历所有的枚举量或者需要建立一个用枚举量做下标的数组的时候就很方便了

枚举量

声明枚举量的时候可以指定值
enum COLOR { RED=1,YELLOW,GREEN = 5};

 枚举只是int

 —— 即使给枚举类型的变量赋不存在的整数值也没有任何warning或error

—— 虽然枚举类型可以当作类型使用,但是实际上很(bu)少(hao)用
—— 如果有意义上排比的名字,用枚举比const int方便
—— 枚举比宏(macro)好,因为枚举有int类型

实际C中枚举主要用于定义符号量,很少当做枚举类型使用。

11.2  结构

声明结构类型

# include <stdio.h>

// 声明结构类型
struct date{
	int day;
	int month;
	int year;
};//注意结尾分号

int main(int argc, char const *argv[])
{
	struct date today;// 定义这种结构类型的结构变量
	
	today.day=3;
	today.month=2;
	today.year=2023;
	
	printf("Today's date is %i-%i-%i.\n",
		today.year,today.month,today.day);
	
	return 0;
}

在函数内外?

——  和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
——  所以通常在函数外部声明结构类型这样就可以被多个函数所使用了

声明结构的形式

——  对于第一和第三种形式,都声明了结构point。
——  但是第二种形式没有声明point,只是定义了两个变量

结构变量

结构变量的初始化

# include <stdio.h>

// 声明结构类型
struct date{
	int day;
	int month;
	int year;
};//注意结尾分号

int main(int argc, char const *argv[])
{
	struct date today;// 定义这种结构类型的结构变量
	
	// 结构变量的初始化(方式一)
	today.day=06;
	today.month=02;
	today.year=2023;
	
	printf("Today's date is %i-%i-%i.\n",
		today.year,today.month,today.day);
	
	// 结构变量的初始化(方式二)
	struct date tomorrow = {07,02,2023};
	struct date thismonth = {.month = 02,.year = 2023};// 没给的值为0
	
	printf("Tomorrow's date is %i-%i-%i.\n",
		tomorrow.year,tomorrow.month,tomorrow.day);
	printf("This month is %i-%i-%i.\n",
		thismonth.year,thismonth.month,thismonth.day);
	
	return 0;
}

结构成员

结构和数组有点像
数组用 []运算符 和下标访问其成员:a[0] = 10;
结构用 .运算符 和名字访问其成员:today.day、student.firstName、P1.x、P1.y;

结构运算

—  要访问整个结构,直接用结构变量的名字
—  对于整个结构,可以做赋值、取地址,也可以传递给函数参数
—  p1 = (struct point){5,10};         //相当于p1.x = 5;p1.y = 10;
—  p1 = p2;                                     //相当于p1.x = p2.x;  p1.y = p2.y;
数组无法做这两种运算。

结构指针

和数组不同,结构变量的名字并不是结构变量的地址,必须使用&取地址运算符;
struct date *pDate = &today;

# include <stdio.h>

// 声明结构类型
struct date{
	int day;
	int month;
	int year;
};//注意结尾分号

int main(int argc, char const *argv[])
{
	struct date today;// 定义这种结构类型的结构变量
	
	// 结构变量的初始化(方式一)
	today.day=06;
	today.month=02;
	today.year=2023;
	
	printf("Today's date is %i-%i-%i.\n",
		today.year,today.month,today.day);
	
	// 结构变量的初始化(方式二)
	struct date tomorrow = {07,02,2023};
	struct date thismonth = {.month = 02,.year = 2023};// 没给的值为0
	
	printf("Tomorrow's date is %i-%i-%i.\n",
		tomorrow.year,tomorrow.month,tomorrow.day);
	printf("This month is %i-%i-%i.\n",
		thismonth.year,thismonth.month,thismonth.day);
	
	struct date Oneday;
	// 结构运算
	Oneday = today;
	Oneday.year = 2022;
	
	printf("Today's date is %i-%i-%i.\n",
		Oneday.year,Oneday.month,Oneday.day);
	
	struct date *pDate = &today;	// 结构指针取地址
	printf("Address of today is %p\n",pDate);
	
	return 0;
}

结构与函数

结构类型也可作为函数参数
int numberOfDays(struct date d)
—  整个结构可以作为参数的值传入函数:这时候是在函数内新建一个结构变量,并复制调用者的结构的
—  也可以返回一个结构
—  这与数组完全不同

#include <stdio.h>
#include <stdbool.h>

struct date{
	int month;
	int day;
	int year;
};

bool isLeap(struct date d);//判断是否闰年
int numberOfDays(struct date d) ;

int main(int argc, char const *argv [])
{
	struct date today,tomorrow;
	
	printf("Enter today 's date (mm dd yyyy): " );
	scanf("%i %i %i", &today.month,&today.day,&today.year);// 取成员优先级更高,后取地址
	// 计算明天
	if ( today.day != numberOfDays(today) ) {
		tomorrow.day = today.day+1;
		tomorrow . month = today. month;
		tomorrow. year = today.year;
	}else if ( today.month == 12 ) {
		tomorrow.day = 1;
		tomorrow.month = 1;
		tomorrow.year = today.year+1;
	}else {
		tomorrow.day = 1;
		tomorrow.month = today. month+1;
		tomorrow.year = today.year;
	}
	printf( "Tomorrow's date is %i-%i-%i.\n",
		tomorrow.year, tomorrow. month, tomorrow.day) ;
	return 0;
}

int numberOfDays ( struct date d){
	int days;
	const int daysPerMonth[12] = {31,28,31,30,31,30,31,31,30,31,30,31};// 每个月多少天
	// 若为闰年 2月为 29 天
	if ( d.month == 2 && isLeap(d) )
		days = 29;
	else
		days = daysPerMonth[d.month-1];// -1 数组从0开始数
	
	return days;
}

bool isLeap( struct date d){
	bool leap = false;
	//判断是否闰年
	if ( (d.year %4 ==0 && d.year %100 !=0 ) || d.year%400==0 )
		leap = true;
	
	return leap;
}

 输入结构

没有直接的方式可以一次scanf一个结构:
如果我们打算写一个函数来读入结构:

解决的方案 

  •  之前的方案,把一个结构传入了函数,然后在函数中操作,但是没有返回向去
    • 问题在于传入函数的是外面那个结构的克隆体而不是指针
    • 传入结构和传入数组是不同的
  • 在这个输入函数中,完全可以创建一个临时的结构变量,然后把这个结构返回给调用者

 在函数中建立一个拷贝来回传递,既费空间又费时间。更好的方式是传指针。

 结构指针作为参数

  • K&R说过(p.l3l)
  • “lf a large structure is to be passed to afunction, it is generally more efficient topass a pointer than to copy the wholestructure".

 指向结构的指针

用 -> 表示指针所指的结构变量中的成员

#include<stdio.h> 

struct point{
	int x;
	int y;
}p;

struct point* getstruct(struct point*);
void output(struct point);
void print(const struct point *p);

int main(int argc, char const *argv[])
{
	struct point y = {0,0};
	getstruct(&y);		// 传指针进入getstruct
	output(y);
	output(*getstruct(&y));
	print(getstruct(&y));	//&y是y的地址,为指针传入getstruct,getstruct传出指针给print。
	return 0; 
}

struct point* getstruct(struct point *p)
{
	scanf("%d", &p->x);
	scanf("%d", &p->y);
	printf("%d, %d\n",p->x,p->y);
	return p;						// 返回指针
}//return p; 传入一个参数,对其做处理后再 返回 该参数的函数,方便套用在其他函数中。

void output(struct point p){
	printf("%d, %d\n",p.x,p.y);
}

void print(const struct point *p)//const 保证此函数不对内容做修改
{
	printf("%d %d", p->x, p->y);	//传入的是个指针,是地址,需要p->x得到结构指针指向的内容,从而打印
}

注释:

#include<stdio.h> 

struct point{
	int x;
	int y;
}p;

struct point* getstruct(struct point*);
void output(struct point);
void print(const struct point *p);

int main(int argc, char const *argv[])
{
	struct point y = {0,0};
	getstruct(&y);		// 传指针进入getstruct
	output(y);
	printf("___________________________\n");
	output(*getstruct(&y)); // getstruct传出的是指针,先要*,即为取指针所指的地址上的变量
	printf("___________________________\n");
	print(getstruct(&y));	// &y是y的地址,为指针传入getstruct,getstruct传出指针给print。
	
	printf("___________________________\n");
	getstruct(&y)->x = 0;	// getstruct传出指针->指向结构变量中的x,对其进行赋值。
	print(&y);				// 打印发现 其x已经改为0.
	
	printf("___________________________\n");
	*getstruct(&y) = (struct point ){1,2};	// getstruct传出指针,*取出结构变量,进行结构运算,令结构变量y中的x = 1,y = 2
	print(&y);				// 打印发现 其x = 1 y = 2
	return 0; 
}

struct point* getstruct(struct point *p)
{
	scanf("%d", &p->x);
	scanf("%d", &p->y);
	printf("getsturct: %d, %d\n",p->x,p->y);
	return p;						// 返回指针
}//return p; 传入一个参数,对其做处理后再 返回 该参数的函数,方便套用在其他函数中。

void output(struct point p){
	printf("output: %d, %d\n",p.x,p.y);
}

void print(const struct point *p)//const 保证此函数不对内容做修改
{
	printf("print: %d %d\n", p->x, p->y);	//传入的是个指针,是地址,需要p->x得到结构指针指向的内容,从而打印
}

结构中的结构

结构数组

struct date dates[100];                —— 100个date组成的数组,数组的每一个元素是date结构变量

struct date dates[] = {{4,5,2005},{2,4,2005}};

#include <stdio.h>

struct time {
	int hour;
	int minutes;
	int seconds;
};

struct time timeUpdate(struct time now);	// 给struct time 返回struct time

int main(void)
{
	struct time testTimes[5] = {
		{11,59,59}, {12,0,0}, {1,29,59},{23,59,59},{19,12,27}};
	
	int i;
	for ( i=0; i<5; ++i ) 		// 遍历数组
	{
		printf( "Time is %.2i:%.2i:%.2i",
			testTimes[i].hour,testTimes[i].minutes,testTimes[i].seconds);
		
		testTimes[i] = timeUpdate(testTimes[i]);
		
		printf(" ...one second later it's %.2i:%.2i:%.2i\n" ,
			testTimes[i].hour,testTimes[i].minutes,testTimes[i].seconds);
	}
	return 0;
}

struct time timeUpdate(struct time now){
	++now.seconds;
	if ( now.seconds == 60 ) {
		now.seconds = 0;
		++now.minutes;
		if ( now.minutes == 60 ) {
			now.minutes = 0;
			++now.hour;
			if ( now .hour == 24){
				now.hour = 0;
			}
		}
	}
	return now;
}

结构中的结构

struct dateAndTime{
        struct date sdate;
        struct time stime;
}

嵌套的结构

 pt1是个结构本身,不是指针。

#include <stdio.h>

struct point{
	int x;
	int y;
};

struct rectangle {
	struct point p1;
	struct point p2;
};

void printRect(struct rectangle r){
	printf( "<%d, %d> to <%d,%d>\n",r.p1.x,r.p1.y,r.p2.x,r.p2.y);
}

int main(int argc, char const *argv [])
{
	struct rectangle rects[] = {
		{{1, 2}, {3, 4}},
		{{5, 6}, {7, 8}}
	}; // 2 rectangles
	for( int i=0; i<2 ; i++) {
		printRect( rects[i] );
	}
	return 0;
}

自定义数据类型(typedef)

C语言提供了一个叫做typedef的功能来声明 一个已有的数据类型的新名字。如

typedef int Length;                ——使得 Length 成为 int 类型的别名

这样 Length 这个名字就可以代替int出现在遍历定义和参数声明的地方了:

Length a,b len;
Length numbers[10];

声明新的类型的名字:新的名字是某种类型的别名,改善程序的可读性

 Date就是sturct ADate 
int64_t 就是long型

 11.3 联合

对于 struct 来说,有俩成员,随时可以使用其中任何一个
而 union 成员占据相同内存空间,任何成员变量,占据的空间只有一份。

联合起来使用同一个空间,一个用了另一个就不能用。通过 i 填入4,4就再内存空间里,通过c填入一个值 ' a ',就在那儿了;而当再通过 i 填入0xDEADBEEF时候,就把原来c里面的'a' 冲掉了,但c 还有值。

union AnElT{
    int a;
    char b;
}elt1,elt2;

elt1.i = 4;
elt2.c = 'a';
elt2.i = 0xDEADBEEF;

 union的常用场合:

#include <stdio.h>

typedef union {
	int i;
	char ch[sizeof(int)];	//数组大小sizeof(int),不同平台int大小可能不同
}CHI;

int main(int argc,char const *argv []){
	CHI chi;
	int i;
	chi.i = 1234;		// 通过i写入1234
	for ( i=0; i<sizeof(int); i++ )		// 遍历数组,看ch是什么内容
	{
		printf("%02hhX", chi.ch[i] );		// 希望输出16进制X,一个字节不扩展,显示为2个十六进制数字,不到10补个0 
	}
	printf("\n");
	
	return 0;
}

 输出结果:

1234的十六进制为 0x02D4 

现在用的CPU为x86的CPU,为小端的机器,低位在前,高位在后。

通过这种方式得到一个整数、double等的内部的各个字节。做文件操作,将整数以二进制写到文件里去。。。读写中间的媒介。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值