一、问题引入
CC13 KiKi定义电子日历类:(牛客网)
描述:
KiKi学习了面向对象技术,学会了通过封装属性(变量)和行为(函数)定义类,现在他要设计一个电子日历类TDate。
它有3个私有数据成员:Month,Day,Year和若干个公有成员函数,要求:
(1)带有默认形参值的构造函数,默认值为0, 0, 0;
(2)输出日期函数,用“日/月/年”格式输出日期;
(3)设置日期函数,从键盘输入年、月、日。
输入描述:
一行,三个整数,用空格分隔,分别表示年、月、日。
输出描述:
一行,用“日/月/年”格式输出日期。
输入:
2019 12 30
输出:
30/12/2019
二、铺垫知识
1、指针
1.1 基础指针
指针是什么?首先,它是一个值,这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。
字符*表示指针,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。比如,char*表示一个指向字符的指针,float*表示一个指向float类型的值的指针。
int* intPtr;//变量intPtr,它是一个指针,指向的内存地址存放的是一个整数。
*这个符号除了表示指针以外,还可以作为运算符,用来取出指针变量所指向的内存地址里面的值。
void increment(int* p) {
*p = *p + 1;//参数值+1操作
}
1.2 函数指针
函数本身就是一段内存里面的代码,C 语言允许通过指针获取函数。指向函数的指针存储着函数代码的起始处地址。
声明一个数据指针时,必须声明指针指向的数据类型。声明一个函数指针也必须声明指向的函数类型。为了指向函数类型,要指明函数签名,即函数返回值类型和形参类型。例如
void ToUpper(char*);
ToUpper()函数的类型是“带char*类型参数,返回值类型是void的函数”。声明一个指针指向该函数类型:
void (*ptr)(char*);
这个ptr指针可以指向一切“返回值类型为void,参数类型为char*”的函数。
void print(int a) {
printf("%d\n", a);
}
void (*print_ptr)(int) = &print;
//变量print_ptr是一个函数指针,它指向函数print()的地址。函数print()的地址可以用&print获得。比较特殊的是,C 语言还规定,函数名本身就是指向函数代码的指针,通过函数名就能获取函数地址。也就是说,print和&print是一回事。
有了函数指针,通过它也可以调用函数。
(*print_ptr)(10);
// 等同于
print(10);
2、typedef命令
typedef命令用来为某个类型起别名。
typedef type new_name;
typedef int elementType;//给现存类型取别名elementType
typedef struct linkNode{} link_Node;//给结构体取别名link_Node
typedef void (*func)(int);//给函数指针取别名func
typedef int five_ints[5];//给数组取别名five_ints
typedef理解:
总结一句话:“加不加typedef,类型是一样的“,这句话可以这样理解:
没加typedef之前如果是个数组,那么加typedef之后就是数组类型;
没加typedef之前如果是个函数指针,那么加typedef之后就是函数指针类型;
没加typedef之前如果是个指针数组,那么加typedef之后就是指针数组类型 ————————————————
原文链接:https://blog.csdn.net/JUIU9527/article/details/127910852
3、结构体
3.1 结构体基础
C 语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起。
struct Person{
char sex;
int age;
}
struct Person person;
person.sex='男';
person.age=11;
除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。
struct Person person={'男',11};
注意,大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。否则,必须为每个值指定属性名。
struct Person person={.sex='男',.age=11};
3.2 结构体指针
通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性,就可以影响到函数外部。
struct 指针传入函数的写法如下:
void happy(struct turtle* t) {
}
happy(&myTurtle);
t是 struct 结构的指针,调用函数时传入的是指针。struct 类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成&myTurtle。
函数内部也必须使用(*t).age的写法,从指针拿到 struct 结构本身。
void happy(struct turtle* t) {
(*t).age = (*t).age + 1;
}
(*t).age不能写成*t.age,因为点运算符.的优先级高于*。*t.age这种写法会将t.age看成一个指针,然后取它对应的值,会出现无法预料的结果。
(*t).age这样的写法很麻烦。C 语言就引入了一个新的箭头运算符(->),可以从 struct 指针上直接获取属性,大大增强了代码的可读性。
void happy(struct turtle* t) {
t->age = t->age + 1;
}
总结一下,对于 struct 变量名,使用点运算符(.)获取属性;对于 struct 变量指针,使用箭头运算符(->)获取属性。以变量myStruct为例,假设ptr是它的指针,那么下面三种写法是同一回事。(来源:阮一峰《C语言教程》)
// ptr == &myStruct
myStruct.prop == (*ptr).prop == ptr->prop
4、scanf()函数
scanf()函数用于读取用户的键盘输入。
scanf("%d%d%f%f", &i, &j, &x, &y);
scanf()处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符等。所以,用户输入的数据之间,有一个或多个空格不影响scanf()解读数据。另外,用户使用回车键,将输入分成几行,也不影响解读。
有时,用户的输入可能不符合预定的格式。
//如果用户输入2020-01-01,就会正确解读出年、月、日。问题是用户可能输入其他格式,比如2020/01/01,这种情况下,scanf()解析数据就会失败。
scanf("%d-%d-%d", &year, &month, &day);
为了避免这种情况,scanf()提供了一个赋值忽略符*。只要把*加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。
//%*c就是在占位符的百分号后面,加入了赋值忽略符*,表示这个占位符没有对应的变量,解读后不必返回。
scanf("%d%*c%d%*c%d", &year, &month, &day);
三、我的代码
#include <stdio.h>
typedef struct TDate TDdate;
typedef void (*Init)(TDdate*);
typedef void (*Set)(TDdate*, int, int, int);
typedef void (*Show)(TDdate);
struct TDate {
int day;
int month;
int year;
Init init;
Set set;
Show show;
};
void InitDate(TDdate* tDate) {
tDate->day = tDate->month = tDate->year = 0;
}
void SetDate(TDdate* tDate, int y, int m, int d) {
tDate->day = d;
tDate->month = m;
tDate->year = y;
}
void ShowDate(TDdate tDate) {
printf("%d/%d/%d", tDate.day, tDate.month, tDate.year);
}
int main() {
TDdate tDate = {.init = InitDate, .set = SetDate, .show = ShowDate};
tDate.init(&tDate);
int y, m, d;
scanf("%d%*c%d%*c%d", &y, &m, &d);
tDate.set(&tDate, y, m, d);
tDate.show(tDate);
return 0;
}