结构体还是结构体指针?
缘由:
上周某日晚上的一个项目进度跟进会上,项目中有一个涉及到一个比较大的结构体,然后内部嵌套了好些结构体。后来我问了句:“为啥使用的是结构体,而不是结构体指针?” 同事答到:“都一样”。后来,讨论了一些“如果你嵌套的那个结构体非常大的话,而这个结构体在使用过程中没有被初使化用到,这样会很浪费内存,这种情况使用结构体指针会比较合适”。
由于当时会议的重点不在这里,草草过了。后面越想越不对,这应该是一个值得思考的问题,嵌套时:是使用结构体还是结构体指针,应该是个哲学问题~~~。不过可以从一些角度来分析这个问题
不同角度分析:
- 从内存使用上
- 抽象的关系上
- 嵌套和继承/组合的关系
- 参考优秀的代码
从内存使用上
正如前因提到的:
- 如果这个被嵌套的结构体字段很多;比如200个字段
- 而这个被嵌套的结构体并不是很个场景都会用的上
这时候用结构体指针可以省不少的空间,就很有意义;那就为了省内存而全使用结构体指针?
抽象的关系上
编程本质上也是给问题建模,抽象问题,代码也用来描述这些抽象之间的关系的,所以就有了下面的区别:
- 嵌套的是结构体:有种“包含”或“属于”的关系。内部的结构体“属于”这个外部的在大结构体,内部是外部结构体的组成部分,是它的一员。比如,一个人属于这个部门的人;在比如,协议数据报中,嵌套的关系,一个结构体属于一个结构体
- 嵌套的是结构体指针:有种引用的关系。内部的结构体不属于你,但会“用到”你。比如,为了完成一个项目,这时候需要由不同的部门抽调人过来组成一个项目团队(结构体),这个团队会“用到(引用)”其它部门的人,我在某一时期,某个阶段可能会用到你,也可能不会用到你(的功能)
结构体嵌套和继承/组合的关系
后来突然冒出一个问题:类的继承如何在C语言中来实现?大概就是用嵌套结构体吧
业内有名名言:“组合优于继承”,这时候:
- 继承就是结构体嵌套结构体。(子类包含父类中的所有成员)
- 而组合就是结构体嵌套结构体指针。(因为里面的对象都要new,而new是在heap上,所以是指针、引用)
所以根据这句名言的结论就是:优先使用嵌套结构体指针?
参考优秀的代码
有时候不知道怎么做合适,可以去参考下优秀的代码,归纳总结下,看看别人是怎么做的,参考参考(抄一抄);下面linux源码,进程的结构体表示
struct task_struct {
/* these are hardcoded - don't touch */
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
unsigned long signal;
unsigned long blocked; /* bitmap of masked signals */
unsigned long flags; /* per process flags, defined below */
int errno;
long debugreg[8]; /* Hardware debugging registers */
struct exec_domain *exec_domain;
/* various fields */
struct linux_binfmt *binfmt;
struct task_struct *next_task, *prev_task;
struct task_struct *next_run, *prev_run;
unsigned long saved_kernel_stack;
unsigned long kernel_stack_page;
int exit_code, exit_signal;
/* ??? */
unsigned long personality;
int dumpable:1;
int did_exec:1;
/* shouldn't this be pid_t? */
int pid;
int pgrp;
int tty_old_pgrp;
int session;
/* boolean value for session group leader */
int leader;
int groups[NGROUPS];
/*
* pointers to (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->p_pptr->pid)
*/
struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
struct wait_queue *wait_chldexit; /* for wait4() */
unsigned short uid,euid,suid,fsuid;
unsigned short gid,egid,sgid,fsgid;
unsigned long timeout, policy, rt_priority;
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;
long utime, stime, cutime, cstime, start_time;
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1;
unsigned long swap_address;
unsigned long old_maj_flt; /* old value of maj_flt */
unsigned long dec_flt; /* page fault count of the last time */
unsigned long swap_cnt; /* number of pages to swap on next pass */
/* limits */
struct rlimit rlim[RLIM_NLIMITS];
unsigned short used_math;
char comm[16];
/* file system info */
int link_count;
struct tty_struct *tty; /* NULL if no tty */
/* ipc stuff */
struct sem_undo *semundo;
struct sem_queue *semsleeping;
/* ldt for this task - used by Wine. If NULL, default_ldt is used */
struct desc_struct *ldt;
/* tss for this task */
struct thread_struct tss;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* memory management info */
struct mm_struct *mm;
/* signal handlers */
struct signal_struct *sig;
#ifdef __SMP__
int processor;
int last_processor;
int lock_depth; /* Lock depth. We can context switch in and out of holding a syscall kernel lock... */
#endif
};
其中除了struct thread_struct tss
和struct rlimit rlim[RLIM_NLIMITS]
用的结构体,一个是线程,一个是进程资源大小的限制,更像是在表达这俩就“属于”进程,必不可少。其它的ipc,fs,memory management,signal handlers,更像是为了组合到一起来完成任务,会“用到”你们(的功能),它们各自有独立的功能。
总结
好像结论已经很明显了,更加倾向于从抽象的角度看这个问题,如果是有“包含”或“属于”的关系在里面,就用嵌套结构体;而如果是有“组合”到一起完成某种功能,它承担了一些独特的任务功能,这时可以用嵌套结构体指针。