1.变量与指针
要明确变量是能读能写,存于内存的
而指针是保存地址,在32位处理器中,指针永远是占四个字节。无论其数据类型是什么
2.关键字
volatile:易变量,让编译器不优化掉其定义的数据。适用于在硬件寄存器中使用,让cpu时刻读取ram中某个变量的值。确保硬件电路有效。
const:用来定义某些不想被更改的常量
static:局部变量,让外部文件无法调用该变量。只能在本文件中使用。
extern:外部变量,可通过声明一个外部变量并且include对应文件中也要再次声明。
需要注意的是:extern是声明,不可进行定义如 extern int a =1 是错的。
3.结构体
最好使用typedef方便使用,另外要注意对齐,即结构体所占字节是看所定义类型所占字节最大值的倍数。
struct student{
char a;//1字节
char b;//1字节
int c;//4字节
}
struct student s1 ={'A','B',20} //占8字节,前四个字节中有两个被浪费。此时即为变量,占据了内存
typedef struct student{
char a;
char b;
int c;
}STU;
STU s1 ={'A','B',20}
4.指针赋值与使用
实际上指针赋值与变量赋值类似
/*首先要明确普通变量定义时候的步骤
int a;
a = 123;
1.cpu读取flash得到指令 2.cpu执行指令往ram里面写入变量。
要先得到a的地址,获取数据,将数据写入地址
而指针赋值实际上也是类似
*/
int c;
int *p;
p = &c
*p= 123
/*步骤为:1.将c的地址放入p 2.将数据123写入*p,也就是往c的地址写入数据,因此c就变为123 */
并且在进行指针赋值时要注意数据类型问题,特别是结构体还有可能造成其他数据丢失。
STU s1 ={'A','B',20}
int *p;
p = (int *)&s1.a; //此处注意要数据转换否则报错
*p = 'P'
printf("a = %c\r\n", s1.a);
printf("b = %c\r\n", s1.b);
/*此时运行出来的结果是
a='P' b=
其原因是由于指针在给s1.c赋值的时候,由于*p是四个字节的,
将结构体的s1.b所占的一个字节也占据了,因此显示为空*/
还有一点值得注意
STU s1 ={'A','B',20}
STU s2 ={'1','2',10}
int *p;
struct int * pt;
pt = &s1; //pt是一个指针,因此对应的应该为地址,4字节对应4字节
*pt = s2; //*pt是一个结构体,因此可以直接赋值结构体,8字节对应8字节
pt->c = 30 //也可以用此方式指向结构体某个值进行赋值
/*输出结果为s1结构体内容变为s2结构体内容*/
5.结构体嵌套与函数指针,结构体指针
在使用结构体嵌套的过程中要注意,在给嵌套的结构体赋值的时候要取地址。并且在使用的使用要明确指针用的是->而不是.
typedef struct student{
char *name;
int age;
struct student *classmate; //这里如果不加指针,那就变成套娃了,也无法确定结构体所占大小。
}student,*pstudent;
student s1 = {"zhangsan",20,NULL}
student s2 = {"lisi",30,NULL}
s1.classmate = &s2; //结构体指针取另一个结构体的地址,s1是结构体,直接用.取其中的结构体指针。
printf("s1.classmate = %s/r/n",s1.classmate->name) //classmate为指针用->指向name。
在使用函数指针的时候要和指针函数区分。结构体中不可放入指针函数。通过一个数组来调用结构体中的函数指针,从而让程序跑循环更简便。
typedef struct student{
char *name;
int age;
struct student *classmate;
void (*action)(void) //如果函数不加括号就变成了指针函数,指针函数放在结构体里会报错
}student,*pstudent;
void play(void)
{
printf("play");
}
void sing(void)
{
printf("sing");
}
student s1 ={"zhangsan",20,NULL,play};
student s2 ={"lisi",30,NULL,sing };
/*这里运用函数指针的时候可以直接用函数名,也可以用&play */
/*在使用循环的时候调用s1,s2比较麻烦,可以直接用一下方法 */
ss[2] = {{"zhangsan",20,NULL,play},{"lisi",30,NULL,sing }};
for(i=0;i<2;i++)
{
ss[i].action(); //ss是结构体直接用.引用函数。
]
结构体指针
/*假设有产品a和b,epprom中写好了硬件a和b的版本。a和b各有一个打印的功能要实现,
可以直接if判断,但当产品量多的时候,经常用if或者case的话就很乱,而且改动麻烦
通过结构体指针来应对复杂多样的版本。*/
typedef struct lcd_operation{
int type;
void (*act)(void)
}lcd_operation,*plcd_operation;
void read_epprom(void)
{
return 0; //读取硬件版本
}
void lcd_a_act(void)
{
printf("lcd_a")
}
void lcd_b_act(void)
{
printf("lcd_b")
}
lcd_operation lcds[] = {{0,lcd_a_act},{1,lcd_b_act}};
plcd_operation get_lcd()
{
int type = read_epprom();
return &lcds[type]; //这里使用取地址,取地址只要四个字节,直接返回结构体要8字节
}
int main()
{
plcd_operation lcd; //定义结构体指针
lcd = get_lcd(); //把确定的结构体地址传给指针
lcd->act();
}
6.链表
在结构体中定义一个指向下一个结构体的指针,给头指针赋第一个结构体的地址,从而做到用对应的结构体之间可以通过前一个结构体的指针来访问后一个结构体。
typedef struct student{
char *name;
struct student *next
}student,*pstudent;
student s1 = {"A",NULL};
student s2 = {"B",NULL};
student s3 = {"C",NULL};
pstudent head =NULL;
int main()
{
s1.next = &s1;
s2.next = &s2;
s3.next = NULL;
head = &s1;
while(head)
{
printf("%s\r\n",head->next);
head = head->next; //让指针指向下一个结构体地址
}
}
链表插入与删除
这里面插入删除都有类似的判断,先看我插入和删除的是不是在head后的那一项,如果是直接指针head指过去对应的。否则进行遍历链表找到需要的位置进行处理。
typedef struct student{
char *name;
struct student *next
}student,*pstudent;
student s1 = {"A",NULL};
student s2 = {"B",NULL};
student s3 = {"C",NULL};
pstudent head =NULL;
void insert_student(pstudent ss)
{
pstudent last;
if(head == NULL) //如果只有一个结构体,直接在其后面插入所要的数据
{
head->next = &ss;
ss->next =NULL;
}
else //否则找最后一项,将最后一项的指针指向要插入的地址。
{
last = head;
while(1) //这里的while可以改成while(last->next)减少一个if语句
{
if(last->next ==NULL)
break;
else
last =last->next;
}
last->next = &ss;
ss->next = NULL;
}
}
void remove_student(pstudent ss)
{
pstudent left;
if(head == ss) //若除head头指针外只有一个,直接让head->next指向NULL;
{
head->next =NULL;
}
else //否则遍历链表,找到要删除的链表的前一项,将前一项直接指向后一项,跳过中间。
{
left = head;
while(left)
{
if(left->next == ss) //找到要删的结构体指针
break;
else
left = left->next;
}
left->next = ss->next;
}
}
int main()
{
insert_student(&s1);
insert_student(&s2);
insert_student(&s3);
while(head)
{
printf("%s\r\n",head->next);
head = head->next; //让指针指向下一个结构体地址
}
head =&s1; //这里不再取地址会导致第一个while循环后head就被指向null从而不触发第二个while
remove_student(&s2);
while(head)
{
printf("%s\r\n",head->next);
head = head->next; //让指针指向下一个结构体地址
}
}
值得注意的是在main里的这两个打印循环并不好,在一开始没有重新让head指向s1导致打印不出想要的效果。所以最好是用一个缓存数据来保存head而不是直接改变head的值(实际上在插入和删除函数里的last和left也是类似的思想),也就可以不用重新给head指向s1.
void printf_student(void)
{
pstudent temp = head;
while(temp)
{
printf("%s\r\n",temp->next);
temp = temp->next;
}
}
int main()
{
insert_student(&s1);
insert_student(&s2);
insert_student(&s3);
printf_student();
remove_student(&s2);
printf_student();
}