一、枚举类型
1、枚举类型简介
为某些整数定义一个别名,可以用预处理指令#define
来完成这项工作:
#define MON 1
在此,我们定义一种新的数据类型,希望它能完成同样的工作。这种新的数据类型就是枚举类型,英文原型 enumerate v. 枚举,列举,历数。以下代码定义了这种新的数据类型 – 枚举型:
enum DAY{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
- 枚举型是一个集合,集合中的元素(枚举成员)是一些命名了的整型常量,元素之间用逗号隔开。
DAY
是一个自定义的标识符,可以看成这个集合的名字,是一个可选项,即可有可无的项。- 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1.
- 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
- 枚举类型是预处理指令
#define
的替代。 - 类型定义以分号结束。
2、枚举类型对变量进行声明
方法一: 枚举类型的定义与枚举类型变量的声明分开
enum DAY{
MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY goodDay; //变量 goodDay 的类型为枚举型 enum DAY
方法二: 枚举类型的定义与枚举类型变量的声明同时进行:
enum{ //跟第一个定义不同的是,此处的标号 DAY 省略,这是允许的。
jia = 0, saturday, sunday = 0, monday //每个值为:jia=0, saturday=1, sunday=0, monday=1
} workday; //变量 workday 的类型为 enum{...}
enum week { Mon = 1, Tue, Wed, Thu, Fri, Sat, Sun} days; //变量 days 的类型为枚举型 enum week
enum BOOLEAN { false, true } end_flag, match_flag; //定义枚举类型并声明了两个枚举类型的变量
方法三: 用typedef
关键字将枚举类型定义成别名,并利用该别名进行变量声明:
typedef enum wd{ //跟第一个定义不同的是,此处的标号 DAY 省略,这是允许的。
saturday, sunday = 0, monday //每个值为:saturday=0, sunday=0, monday=1
} Workday; //此处的 Workday 为枚举类型 enum{...} 的别名,注意和方法二中 workday 的区分
Workday today, tomorrow; //变量 today 和 tomorrow 的类型为枚举类型 Workday,也即 enum{...}。
- 方法三代码第一行
typedef enum
后面的wd
可以省略。
注意: 不同的枚举类型中,不能存在同名的命名常量:
typedef enum { wed, thu, fri } Workday_1;
typedef enum { wed, sun, mon } Workday_1; //错误,Workday_1和Workday_2中定义了同名的命名常量
//使用Workday_1、Workday_2声明变量后,在给变量赋值时会造成困惑,到底赋的是哪个wed
3、使用枚举类型的变量
3.1 对枚举类型的变量赋值
/*方法一:先声明变量,再对变量赋值*/
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN }; //每个元素的值为:MON=1, TUE=2, WED=3, THU=4, FRI=5, SAT=6, SUN=7
enum DAY yesterday, today, tomorrow; //声明枚举类型变量
yesterday = SAT; //对枚举型变量赋值
/*方法二:声明变量的同时赋初值*/
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
enum DAY yesterday = SAT, today = SUN; //声明变量的同时赋初值
/*方法三:定义类型的同时声明变量,然后对变量赋值*/
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN } yesterday, today;
yesterday = SAT, today = SUN;
/*方法四:类型定义、变量声明、赋初值同时进行*/
enum DAY{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} yesterday = SAT, today = SUN;
3.2 对枚举类型的变量赋整数值时,需进行强制类型转换
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN } yesterday, today;
enum DAY yesterday, today, tomorrow;
yesterday = (enum DAY) 30; //这个是允许的,强制类型转换得以使用了枚举体定义之外的值
today = 3
3.3 使用枚举类型的变量
enum{
BELL = '\a', //注意有逗号
NEWLINE = '\n' //注意没逗号
};
printf("%c %c", BELL, NEWLINE);
4、枚举类型与sizeof运算符
#include <stdio.h>
enum escapes{
BELL = '\a',
BACKSPACE = '\b',
HTAB = '\t',
RETURN = '\r',
NEWLINE = '\n',
VTAB = '\v',
SPACE = ' '
};
enum BOOLEAN { FALSE = 0, TRUE } match_flag;
void main(){
printf("%d bytes \n", sizeof(enum escapes)); //4 bytes
printf("%d bytes \n", sizeof(escapes)); //4 bytes
printf("%d bytes \n", sizeof(SPACE)); //4 bytes
printf("%d bytes \n", sizeof(NEWLINE)); //4 bytes
printf("%d bytes \n", sizeof(FALSE)); //4 bytes
printf("%d bytes \n", sizeof(enum BOOLEAN)); //4 bytes
printf("%d bytes \n", sizeof(BOOLEAN)); //4 bytes
printf("%d bytes \n", sizeof(match_flag)); //4 bytes
printf("%d bytes \n", sizeof(0)); //4 bytes
}
运行结果如下图:
Dev c++ 配置如下图:
Dev c++ 配置如下图32-bit时,上述代码大运行结果仍不变:
原因:
不论是 32 位环境还是 64 位环境,c/c++ 都为整型数字字面值常量分配 4 字节空间。
可以类比:不论是 32 位环境还是 64 位环境,c/c++ 都为 int 分配 4 字节空间。
二、编译过程
- 预处理
处理宏。修改源程序得到另一个源程序,常以.i
作为文件扩展名。 - 编译
将.i
文本文件翻译成.s
文本文件。.s
文件包含了汇编语言程序,汇编语言程序以一种标准的文本格式确切描述一条低级机器语言指令。 - 汇编
将.s
文件翻译成机器语言指令,并打包成可重定位目标程序,一般以.o
作为文件扩展名。可重定位目标程序是二进制文件,它的字节编码是机器语言指令而不是字符。 - 链接
将.o
目标文件组合起来,创建可执行目标文件。
三、内存4区
- 栈区: 由编译器自动分配释放,像局部变量,函数参数,都是在栈区。会随着作用域退出而释放空间。
- 堆区:程序员分配并释放的区域,像malloc©,new(c++)
- 常量数据区(静态区):初始化的全局变量和静态变量在
.bss
段。未初始化的全局变量和未初始化的静态变量在相邻的.data
。const
、#define
、char* ptr = "blabla"
中的blabla
等数据常量放在.rodata
段。 - 代码区
- 特别注意下面这个关于 ‘1、’ 和 ‘3、’ 的例子:
int main()
{
char s[] = "abc123"; //栈
char* p = "abc123"; // abc123\0 在常量区,p在栈上。
}
1、由于两者都是局部变量,所以都是动态存储区内的变量。
2、char s[]
的含义是一个char类型的数组,只是缺省了长度,这个长度将由编译器以赋值的内容来推断。所以这句话相当于char s[7] = "abc123"
,所以是在栈上的。
3、char* p
是一个char类型的指针变量,这个指针指向了一个常量的字符串。常量在常量存储区的.rodata
段。而指针变量因为其是局部变量所以是在栈上的。
四、static 关键字
详解c语言中的static关键字
概念:c语言中的static
关键字有三种用途:用于修饰局部变量、全局变量和函数,修改其数据存储类型
1、 静态局部变量
- 在任意一个函数内部定义的变量就是普通局部变量,初始值随机,出函数自动销毁,存放于栈区。
- 使用
static
修饰的局部变量就是静态局部变量, 编译阶段就已经被分配了内存空间,程序员不初始化,则会被默认初始化为0,存放于静态存储区,函数返回时值保持不变,出函数不销毁,下一次进入函数依然存在。 - 虽然存放在静态存储区,但是作用域仍然是函数内部,局部两个字拥有最高话语权。
- 代码文本中未初始化的静态局部变量,编译器会把它初始化为零。已初始化的静态局部变量,随着函数的第一次调用而被真正初始化。且只初始化一次,也就是说第二次调用函数时,运行到静态局部变量初始化的那行,不会再初始化,而会直接跳过。(这一条,静态局部变量的初始化时机有待考证)
- 问:为什么static变量只能被常量初始化,而不能被变量初始化?
例如:
int a = 10;
static int b = a;
答:这样会报错,变量不能给static变量赋值,因为static变量存放在静态区,它在程序编译阶段就在静态区了,但是普通变量只有在程序运行到该变量的初始化语句的时候才会在栈区分配内存空间,所以static变量在编译阶段被没有初始化分配内存空间的普通变量赋值是错误的,只能用常量初始化。
2、静态全局变量
- 普通全局变量定义在函数体外部,在静态存储区分配存储空间,编译器自动对其初始化。普通全局变量对整个工程可见,其它文件使用关键字
extern
外部声明后可以直接使用。 - 静态全局变量仅对当前文件可见,其它文件不可访问,使用
extern
也没用。其它文件可以定义与其同名的变量,两者互不影响。在定义不需要与其它文件共享的全局变量时,加上static
关键字能够有效降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
3、静态函数(和静态全局变量无异)
- 在函数的返回类型前加上
static
,就是静态函数。例如static int main()
。 - 静态函数只能在声明它的文件可见,其它文件不能引用该函数。不同文件可以使用相同名字的静态函数,非静态函数可以在另一个文件直接引用。
详解c++中的static关键字
1、静态类对象
- 声明为
static
的对象将分配到静态存储区,并且一直作用到程序结束。静态对象也使用和其它普通对象一样的构造函数进行初始化。但是使用static
关键字默认初始化为0仅适用于原始数据类型,不适用于用户自定义的数据类型。
class Abc{
int i;
public:
Abc(){
i=0;
cout << "constructor";
}
~Abc(){
cout << "destructor";
}
};
void f(){
static Abc obj;
}
int main(){
int x=0;
if(x==0){
f();
}
cout << "END";
}
- 输出1:
- 去掉14行
static
关键字的输出2:
- 输出1为什么在
if
条件范围结束时不调用析构函数来销毁obj
对象,这是因为输出1对应的对象obj
是静态的,其作用于程序的整个生命周期,因此在main()
函数退出时才调用obj
对象的析构函数。
2、类中的静态成员变量
- 类中的静态成员变量被所有的类对象所共享,静态成员只有一个存储空间,而对于非静态成员变量,每个类对象(实例)都有自己的拷贝。
- static修饰的变量并不占用类对象(实例)的内存空间。
- 类中的静态成员变量不使用构造函数初始化,因为它们不依赖于对象初始化。
- 类中的静态成员变量必须显式初始化,通常在类外进行初始化。如果未初始化,连接器将给出错误。
- 某个对象修改了静态成员变量,其它对象再访问静态成员变量时,就是被修改之后的值了。(类中的静态成员变量可以类比为ROS参数服务器中的参数,对象可类比为ROS中的节点,每个ROS节点都可以访问ROS参数服务器中的参数,同样地,每个对象都可以访问类中的静态成员变量)
3、类中的静态方法(静态成员函数)
- 这些方法适用于整个类,而不是类特定的对象。
- 可以使用直接成员访问.来调用类中的静态方法。 但是,使用类名和作用域解析::运算符调用类中的静态成员函数更为典型。
- 普通成员函数合一任意地访问静态成员函数和静态数据成员,但是静态成员函数不能访问普通成员变量和普通成员函数,而只能访问类中的静态成员变量和类中的静态成员函数。
五、volatile关键字
volatile应该解释为“直接存取原始内存地址中的value”比较合适,“易变的”这种解释简直有点误导人;
1)并行设备的硬件寄存器(如:状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3)多线程应用中被几个任务共享的变量
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
五、求 struct 的 sizeof()
五、c语言头文件
1、头文件中写入函数的声明即可。#ifdef
、#define
为条件编译,是为了防止函数被重定义。
2、
六、大端模式和小端模式
1、静态成员属性的特点:
①编译阶段分配内存
②所有对象共享同一份数据
③类内声明,类外初始化
class Base
{
public :
static int m_A; //类内声明
};
int Base::m_A = 100; //类外初始化
2、动态多态
动态多态需满足的条件:
①有继承关系
②子类重写父类的虚函数
class Animal
{
public :
virtual void speak() //Speak函数就是虚函数。函数前面加virtual关键字,就成了虚函数,那么编译阶段就不能确定函数调用了。
{cout << "动物在说活。" << endl;}
};
class Cat : public Animal
{
public :
void speak() //子类重写父类函数
{cout << "小猫在说话。" << endl;}
};
动态多态的使用:
父类的指针或引用 指向子类对象
class Cat :public Animal //父类Animal的Cat子类
{
public:
void speak()
{cout << "小猫在说话" << endl;}
};
class Dog :public Animal //父类Animal的Dog子类
{
public:
void speak()
{cout << "小狗在说话" << endl;}
};
void DoSpeak(Animal & animal) //父类的引用
{
animal.speak();
}
void test01()
{
Cat cat; //子类对象
DoSpeak(cat); //父类的引用指向子类对象
Dog dog; //子类对象
DoSpeak(dog); //父类的引用指向子类对象
}
3、面向对象的三大特性:
封装
继承
多态
4、字符串比较大小:
字符串对比主要用于比较两个字符串是否相等,判断谁大谁小的意义并不是很大
string str1 = "hello";
string str2 = "hell0";
str1.compare(str2); //返回值 相等(0),大于(1),小于(-1).
5、STL栈容器:
栈不允许遍历行为 (STL的栈符合先进后出原则)
可以判断栈容器是否为空吗? 可以 empty
栈容器可以返回元素个数吗? 可以 size (可以在压栈的时候计数)
6、基类的析构函数一定要写成虚的,而且一定要有析构
编译器的自动生成是够用的,但是仍然要写一个,否则多态的delete会出问题。
7、关于for循环的循环次数
i < top, i 就取到 top-1,
i <= top, i 就取到 top
for(int i=0; i<top; i++)
for(int i=0; i<=top; i++)
8、CSDN编辑笔记:
不空字符
空一个字符
不空字符
空半个字符
提示:这里描述项目中遇到的问题:
例如:数据传输过程中数据不时出现丢失的情况,偶尔会丢失一部分数据
APP 中接收数据代码:
@Override
public void run() {
bytes = mmInStream.read(buffer);
mHandler.obtainMessage(READ_DATA, bytes, -1, buffer).sendToTarget();
}