您的位置:首页 > 编程语言 > C语言/C++

【C语言学习笔记】结构体、枚举、预处理指令

2015-03-23 11:18 519 查看
第一部分 结构体
结构体是一种构造数据类型,之前我们学过的数组也属于构造数据类型。

一、结构体与数组的区别:

1、数组:只能由多个相同数据类型的数据构成

2、结构体:可以由多个不同数据类型的数据构成

二、结构体的定义

1、定义结构体类型

struct Person
{
//把里面的3个变量,可以称为结构体的成员或属性
int age;    //4个字节
double height;    //8个字节
char *name;    //8个字节
};
2、根据结构体类型,定义结构体变量

// 格式:结构体类型 结构体变量名
// 结构体变量初始化
struct Person p = {20, 1.78, “jake”};
//将各成员的初值,按顺序放在一对大括号{}中,并用逗号分开,一一对应赋值
//注意:只能在定义变量的同时进行初始化赋值,初始化赋值和变量的定义不能分开
//错误的写法
struct Person p;
p = {20, 1.78, “jake”};
四、结构体内存分析

1、struct Person是定义的结构体类型,同int一样,系统没有为数据类型分配存储空间

2、程序运行到定义结构体变量时,系统才分配存储空间

3、我们来看下结构体变量在内存中具体的存储地址

//结构体变量在内存中所占的字节数
int s = sizeof (p);
printf (“s = %d\n”, s);
//结构体变量中成员的地址
printf (“%p - %p - %p\n”, &p.age, &p.height, &p.name);

结果是:s = 24
0x7fff5e7e6bd0 - 0x7fff5e7e6bd8 - 0x7fff5e7e6be0
通过查看结果,我们发现结构体变量中int型变量age占了8个字节,其实,这是因为补齐算法(对齐算法)的原因,结构体所占存储空间的大小一定是结构体变量中最大成员所占字节数的倍数,因为int型占4字节,double型占8字节,指针类型也占8字节,加起来是20字节,不是8个字节的倍数,根据补齐算法,给int型补4个字节之后,就得到了我们上面的结果。

五、结构体注意点

1、除了上面的定义方式,还有另外两种方式

(1)定义类型的同时定义变量

struct Person
{
int age;
double height;
char *name;
} p1;
(2)定义类型的同时定义变量,但是省略了类型名

strcut
{
int age;
char *name;
} stu;
strcut
{
int age;
char *name;
} stu2;
注意:这样的定义方式,不能重复使用结构体类型,如果定义类型时,有类型名,那么就不能再按上面的方式定义了,那样就造成了结构体类型的重复定义,系统是会报错的。

//错误写法
struct Person
{
int age;
double height;
char *name;
} p2;
struct Person
{
int age;
double height;
char *name;
} p3;
2、结构体类型也是有作用域的,可以分为全局变量和局部变量

(1)局部变量:在函数(代码块)内部定义的变量(包括函数的形参),从定义变量的那一行开始,一直到代码块结束

(2)全局变量:在函数外部定义的变量,从定义变量的那一行开始,一直到文件结尾(能被后面的所有函数使用)

3、不能对结构体本身进行递归定义

//错误的写法
struct Student
{
int age;
struct Student stu;
};
六、结构体数组
1、定义结构体数组
// (1)定义结构体类型
struct RankRecord
{
int no;	//序号
char *name;	//姓名
int score;	//积分
};
// (2)定义结构体数组并初始化
struct RankRecord records[3] = {
{1, “jack”, 1000},
{2, “jim”, 800 },
{3, “jake”, 500}
};
2、注意点:

不能用下面的方法给数组元素赋值,是错误的写法

/* 错误写法
records[2] = {4, “rose”, 600};
*/
如果想赋值,应该是

records[2].no = 4;
records[2].name = “rose";
records[2].score = 600;
七、指向结构体的指针

结构体变量也有地址,有地址就有指向它的指针

1、指向结构体的指针的定义

// 1.定义结构体变量并初始化
struct Student
{
int no;
int age;
} stu = {1, 20};
// 2.定义指针,指针变量p指向了struct Student类型的数据
struct Student *p;
//代表指针变量p指向了stu变量
p = &stu;
2、利用指针访问结构体成员
(1)(*p).成员名称
printf (“age = %d, no = %d\n”, (*p).age, (*p).no);

(2)p -> 成员名称
printf (“age = %d, no = %d\n”, p -> age, p -> no);
//p -> 表示:访问指针变量p指向的存储空间
//p -> age表示:继续访问存储空间内的age
八、结构体和函数
#include <stdio.h>
struct Student
{
int age;
int no;
};

void test(struct Student s)
{
s.age = 30;
s.no = 2;
}

void test2(struct Student *p)
{
p->age = 15;
p->no = 2;
}

int main(void)
{
struct Student stu = {28, 1};

test( stu );
printf("age=%d, no=%d\n", stu.age, stu.no);

test2(&stu);
printf("age=%d, no=%d\n", p->age, p->no);

return 0;
}
1、如果结构体作为函数参数,只是将实参结构体所有成员的值对应地赋值给了形参结构体的所有成员,修改函数内部结构体的成员不会影响外面的实参结构体
age=28, no=1
2、如果是传入的是地址,那么改变函数内部结构体的成员那么就会影响外面的实参结构体的成员
age=15, no=2


第二部分 枚举
一、枚举的定义
枚举的定义方式与结构体相似,当变量只允许有固定的几个选择的时候,用枚举

1、第一种方式

// 1.定义枚举类型
enum Season
{
spring,
summer,
autumn,
winter
};
// 2.定义枚举变量
enum Season s = spring;

2、第二种方式
enum Season
{
spring,
summer,
autumn,
winter
} s = spring;
二、枚举使用注意:

1、枚举是一种基本数据类型,编译器会将枚举元素作为整型常量处理,称为枚举常量

2、枚举元素的取值决定于定义时枚举元素排列的先后顺序。默认情况下,第一个枚举元素的为0,以此类推。

第三部分 预处理指令
一、预处理指令的特点:
1、所有的预处理指令都是以#开头,并且结尾没有分号。

2、预处理指令在代码编译成0和1之前执行。

3、预处理的位置不是固定的,函数内外都可以。

4、预处理指令的作用域:从编写指令的那一行开始,一直到文件的结尾。

5、预处理指令有3种:宏定义、条件编译、文件包含
二、宏定义
1、不带参数的宏定义
(1)格式:#define 宏名 字符串
(2)作用:在编译之前,系统会把文件内所有的COUNT替换成5,经常定义常量
#include <stdio.h>

#define COUNT 5

int main(void)
{
int ages[COUNT] = {1, 2, 67, 89, 90};

for (int i = 0; i < COUNT; i++)
{
printf("%d\n", ages[i]);
}
return 0;
}
(3)使用注意:
1> 一般宏名都是用大写字母,如果用小写字母,那么前面加k
2> 在双引号扩起来的字符串中的字符,不会被替换

#define COUNT 5

// 不替换
char * name = "COUNT";
3> 如果需要终止宏定义的作用域,可以用#undef命令
#define COUNT 5

#undef


2、带参数的宏定义
(1)格式:#define 宏名(参数列表) 字符串
(2)作用:用字符串代替宏名,然后把宏名中的参数填入字符串对应的位置
#include <stdio.h>

#define pingfang(a) ((a)*(a))

int main(void)
{
int c = pingfang(5+5)/pingfang(2);
// int c = ((5+5)*(5+5)) / ((2)*(2));

printf("c is %d\n", c);

return 0;
}
(3)使用注意
1> 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串

2> 带参数的宏预处理时,只作替换,不进行任何计算操作。必须用一个小括号括住字符串中的每一个参数。

3> 带参数的宏定义比函数的效率更高,但是宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题。
三、条件编译
1、只有在满足一定条件下,才会进行编译。
2、格式
#define A 5

int main()
{

#if (A == 10)
printf("a是10\n");
#elif (A == 5)
printf("a是5\n");
#else
printf("a是其他值\n");
#endif
return 0;
}
(1)因为开始宏定义了A用5代替,所以,条件判断A==5成立,那么前面和后面的代码都不会被编译,只有printf("a是5\n");会被编译。
(2)条件编译结束后,要在最后面加一个#endif,否则,#else后面的内容就都不会被编译,程序出现缺失。
(3)条件一般是判断宏定义而不是变量,因为条件编译也是在编译之前完成的,变量是在程序执行之后才产生的。
3、其他用法
(1)判断是否进行过宏定义
#if defined(A)
printf("哈哈\n");
#endif
判断如果定义过A这个宏,则编译printf("哈哈\n");
条件取反,如果没有定义过A这个宏,则编译printf("哈哈\n");
#if !defined(A)
printf("哈哈\n");
#endif
(2)#ifdef和#ifndef
1> #ifdef与#if defined用法一致
2> #ifndef与#if !defined用法一致
四、文件包含 #include
1、将后面文件内的所有内容拷贝到#include的位置。
2、<>表示包含系统自带的文件,""表示包含自定义的文件。
3、使用注意
(1)文件包含允许嵌套包含,比如a.h包含b.h,而b.h包含c.h;不允许递归包含,比如a.h包含b.h,而b.h又包含a.h。
(2)如何避免多次包含同一个头文件而降低编译的效率
//头文件中这么写一个条件编译,就可以避免重复编译

#ifndef LISI_H
#define LISI_H
int sum(int a, int b);
#endif
因为第一次包含头文件后,先进行条件编译,#ifndef肯定是成立的,所以编译后面的内容,进行宏定义等等,到#endif结束条件编译。如果后面有重复包含,那么条件编译就不会成立,直接就跳到#endif结束条件编译了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: