C语言标准定义了32个关键字
union声明联合数据类型
- Union declaration - cppreference.com
- 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在 union 中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址
union StateMachine
{char character;int number; char *str; double exp;
};
- 一个 union 只配置一个足够大的空间以来容纳最大长度的数据成员,以上例而言,最大 长度是 double 型态,所以 StateMachine 的空间大小就是 double 数据类型的大小。
- union主要目的是为了压缩数据存储的空间,如果变量不会在同一时间被使用到就可以使用 union
enum声明枚举类型
- Enumeration declaration - cppreference.com
rigister 声明寄存器变量
- Storage class specifiers - cppreference.com
- https://en.wikibooks.org/wiki/C%2B%2B_Programming/Programming_Languages/C%2B%2B/Code/Keywords/register
- 这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对。因为 CPU 的寄存器有限
- 注意: 寄存器在 c 和 c++之间有不同的语义。在 c 语言中,可以通过声明数组寄存器来禁止数组到指针的转换: register int a [1] ;
- 从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用
- 这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
- 存储空间分配不同,auto类型分配在栈上,属于动态存储类别,占动态存储区空间,函数调用结束后自动释放;
- 而static分配在静态存储区,在程序整个运行期间都不释放。两者之间的作用域相同,但生存期不同
- static局部变量在所处模块的初次运行时进行初始化工作,且只初始化一次。
- 对于局部静态变量,如果不赋初值,编译期会自动赋初值0或空字符;而auto类型的初值是不确定的。(对于C++中的class对象例外,class的对象实例如果不初始化,则会自动调用默认构造函数,不管是否是static类型)
- register 变量必须是 能被 CPU 寄存器所接受的类型。意味着 register 变量必须是一个单个的值,并且其长度应小 于或等于整型的长度
volatile说明变量类型可以被隐含改变
- cv (const and volatile) type qualifiers - cppreference.com
- 编 译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
- const volatile修饰的变量 - Jeremy's blog
int i=10;
int j = i;//(1)语句
int k = i;//(2)语句
- 这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候 编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个 值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码 重新从内存里取 i 的值,这样提高了效率。但要注意:(1)、(2)语句之间 i 没有被用作左 值才行。
volatile int i=10;
int j = i;//(3)语句
int k = i;//(4)语句
- volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i 的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在 k 中。这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数 据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
但是注意:在 VC++6.0 中,一般 Debug 模式没有进行代码优化,所以这个关键字的作 用有可能看不出来。你可以同时生成 Debug 版和 Release 版的程序做个测试。
mutable 存储类
- mutable 说明符仅适用于类的对象,这将在本教程的最后进行讲解。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
thread_local 存储类
- 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
- thread_local 说明符可以与 static 或 extern 合并。
- 可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
- 以下演示了可以被声明为 thread_local 的变量:
thread_local int x; // 命名空间下的全局变量
class X
{static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的void foo()
{thread_local std::vector<int> v; // 本地变量
}
定义
- 所谓的定义就是(编译器)创建一个对象,为这个对象分配一块内存,取上一个名字,这个名字就是变量名或对象名。
- 但注意,这个名字一旦和 这块内存匹配起来,它们就同 生共死,终生不离不弃。并且这块内存的位置也不能被改变。
- 一个变量或对象在一定的区 域内(比如函数内,全局等)只能被定义一次,如果定义多次,编译器会提示你重复定义 同一个变量或对象。
声明 两重含义
第一重
- 通知编译器,变量名字 已经匹配到一块内存空间,此刻出现的变量或对象是在别的地方已经定义过了
- 声明可以出现多次
第二重
- 通知编译器,名字已经提前预定了,别的地方再也不能用它来作为变量名或对象名
- 例子:void fun(int i, char c);
例子
- int i; 定义
-
extern int i; 声明
重点:
- 定义声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存
基本数据类型
- 使用sizeof 查看具体平台数据类型的大小
命名规则
- 所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
const int MAX_LENGTH = 100; //这不是常量,而是一个只读变量,具体请往后看
#define FILE_PATH “/usr/tmp”
- 定义变量的同时千万千万别忘了初始化。因为,定义变量时编译器并不一定清空了 这块内存,它的值可能是无效的数据
- 不同类型数据之间的运算要注意精度扩展问题,一般低精度数据将向高精度 数据扩展。
sizeof
- sizeof(p) 和 sizeof(*p)的区别?
- sizeof(p)是指针类型,64位平台是8,32位平台是4
- sizeof(*p) 是指针指向的数据类型的大小,如果是long double就是16,double是8,int 是4
int main(){long double *p = nullptr;std::cout << sizeof(p) <<std::endl;std::cout << sizeof(*p) <<std::endl;
}
int main(){int a[100];std::cout << sizeof(a) <<std::endl; //400std::cout << sizeof(&a) <<std::endl; //8std::cout << sizeof(&a[0]) <<std::endl;//8
}
#include <iostream>int b[100];
void fun(int b[100]){std::cout << sizeof(b) <<std::endl; //8
}int main(){fun(b);
}
#include <iostream>
#include <cstring>int main(){char a[1000];for (int i = 0; i < 1000; ++i) {a[i] = -1 - i;}printf("%d",strlen(a));
}
- for 循环内,当 i 的值为 0 时,a[0]的值为-1。关键就是-1 在内存里面如何存储。
- 计算机系统中,数值一律用补码来表示(存储)。主要原因是使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数 相加时,如果最高位(符号位)有进位,则进位被舍弃。正数的补码与其原码一致;负数的 补码:符号位为 1,其余位为该数绝对值的原码按位取反,然后整个数加 1。
按照负数补码的规则,可以知道-1 的补码为 0xff,-2 的补码为 0xfe......当 i 的值为 127 时,a[127]的值为-128,而-128 是 char 类型数据能表示的最小的负数。当 i 继续增加,a[128] 的值肯定不能是-129。因为这时候发生了溢出,-129 需要 9 位才能存储下来,而 char 类型 数据只有 8 位,所以最高位被丢弃。剩下的 8 位是原来 9 位补码的低 8 位的值,即 0x7f。 当 i 继续增加到 255 的时候,-256 的补码的低 8 位为 0。然后当 i 增加到 256 时,-257 的补 码的低 8 位全为 1,即低八位的补码为 0xff,如此又开始一轮新的循环...... - 按照上面的分析,a[0]到 a[254]里面的值都不为 0,而 a[255]的值为 0。strlen 函数是计 算字符串长度的,并不包含字符串最后的‘\0’。而判断一个字符串是否结束的标志就是看 是否遇到‘\0’。如果遇到‘\0’,则认为本字符串结束。分析到这里,strlen(a)的值为 255 应该完全能理解了。这个问题的关键就是要明白 char 类型默认情况下是有符号的,其表示的值的范围为[-128,127],超出这个范围的值会产生溢 出。另外还要清楚的就是负数的补码怎么表示。弄明白了这两点,这个问题其实就很简单了。
所谓原码就是前面所介绍的二进制定点表示法,即最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小。反码表示法规定:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。补码表示法规定:正数的补码与其原码相同;负数的补码是在其反码的末位加1。根据原码的定义:正零和负零的原码为:+0 : 0000 0000 0000 0000 0000 0000 0000 0000 (32 bit)-0 : 1000 0000 0000 0000 0000 0000 0000 0000而反码为:+0 : 0000 0000 0000 0000 0000 0000 0000 0000-0 : 1111 1111 1111 1111 1111 1111 1111 1111补码为:+0 : 0000 0000 0000 0000 0000 0000 0000 0000-0 : 1 0000 0000 0000 0000 0000 0000 0000 0000可以看出,-0的补码发生溢出,舍弃最高位后,其跟+0在内存的表示一样,都是:0000 0000 0000 0000 0000 0000 0000 0000
switch case
- case 后面只能是整型或字符型的常量或常量表达式(想想字符型数据在内存里 是怎么存的)
- switch case排列顺序
- 把正常情况放在前面,而把异常情况放在后面
- 按执行频率排列 case 语句
- switch 里面不可以使用 continue
- 在switch case 语句中能否使用continue关键字?_Keep Fighting All The Time-CSDN博客_switch语句中的continue
循环代码
- 多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
void
- void *可以指向任何类型的数据
-
void 不能代表一个真实的变量,因为没有内存空间
return关键字
- c++ - Return char* from function - Stack Overflow
return char*
- 函数内新建一个static char数组,这样函数结束数组也不会被销毁 / 使用const char * 字符串存储在常量区
- 函数内部动态申请内存,使用完需要释放内存
- 全局声明数组,不好,别人会改
- 函数返回char* 的解决方案_芒果儿-CSDN博客_c++ 返回char*
const
- case 语句后必须是一个常量,const 修饰的只读变量仍然是变量,所以是不可以的
- 【C语言】const关键字用法 - 代码先锋网
- const 定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define 一样给出的是立即数,所以,const 定义的只读变量在程序运行过程中只有一份拷贝(因为 它是全局的只读变量,存放在静态区),
- 而#define 定义的宏常量在内存中有若干个拷贝。 #define 宏是在预编译阶段进行替换,而 const 修饰的只读变量是在编译的时候确定其值。
- #define 宏没有类型,而 const 修饰的只读变量具有特定的类型。
- const 离哪个近,修饰哪个变量,不可以改变
- 修饰函数的参数 告诉编译器 ,形参输入,在函数体中的不能改变,从而防止了使用者的一些无意的或错误的修改。
- 修饰函数的返回值 const 修饰符也可以修饰函数的返回值,返回值不可被改变。例如: const int Fun (void);
参考链接
- What does sizeof (int) * p semantically mean?
- c - Difference between sizeof(*p) and sizeof(p)? - Stack Overflow
- c语言中的 %u 什么意思啊?_百度知道
struct关键字
- 多种数据组合起来的一个整体,其表现形式是一个结构体
- 传入传出都是结构体的形式,可以压缩传输的参数
- 结构体所占的内存大小是其成员所占内存之和 sizeof(struct),这里还涉及到结构体的内存对齐
- 即使是空的结构体,使用sizeof求内存,其大小是1,主要是为其分配一个地址,空类也是一样的
柔性数组
- 结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员。
- 柔性数组成员允许结构中包含一个大小可变的数组。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用 malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
- 用 sizeof(type_a)得到的只有 4,就是 sizeof(i)=sizeof(int)。那个 0 个元素的数组没有占用空间,而后我们可以进行变长操作了。使用malloc分配内存之后,使用sizeof探测整体的数据大小,仍然是 4
- 柔性数组只是编外人员,不占结构体的编制。只是说 在使用柔性数组时需要把它当作结构体的一个成员,仅此而已。再说白点,柔性数组其实与 结构体没什么关系,只是“挂羊头卖狗肉”而已,算不得结构体的正式成员
- 当然,上面既然用 malloc 函数分配了内存,肯定就需要用 free 函数来释放内存
- 这个柔性数组的概念 实际使用很少
#include <iostream>
#include <cstring>typedef struct st_type{int i;int a[];
}type_a;
int main(){std::cout << sizeof(type_a) << std::endl; //4type_a * p = (type_a*) malloc(sizeof (type_a) + 100 * sizeof(int));std::cout << sizeof(type_a) << std::endl; //4free(p);
}
大端模式 和 小端模式
- 大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
- 小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
- 变量i占4个字节,但只有一个字节的值为1,另外三个字节的值都为0。如果取出低地址上的值为0,毫无疑问,这是大端模式;如果取出低地址上的值为1,毫无疑问,这是小端模式。
使用程序验证
#include <iostream>
#include <cstring>int checkSystem(){union check{int i;char ch;}c;c.i = 1;return (c.ch == 1);
}
int main(){int a = checkSystem();std::cout << a << std::endl;
}
直接查看内存
- 枚举变量的大小,实质是常数所占内存空间的大小(常数为int类型,当前主流的编译器中一般是32位机器和64位机器中int型都是4个字节),枚举类型所占内存大小也是这样。参考链接:enum枚举变量所占内存大小_bulebin的博客-CSDN博客_枚举类型大小