C++学习笔记之基础语法
基础语法
switch和if区别
先看一下switch和if汇编代码的区别:
int a = 3;
00007FF6A1831B4C mov dword ptr [a],3
if (a == 1)
00007FF6A1831B56 cmp dword ptr [a],1
00007FF6A1831B5D jne main+164h (07FF6A1831B74h)
{
cout << a;
00007FF6A1831B5F mov edx,dword ptr [a]
00007FF6A1831B65 mov rcx,qword ptr [__imp_std::cout (07FF6A1841150h)]
00007FF6A1831B6C call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A18411E0h)]
00007FF6A1831B72 jmp main+195h (07FF6A1831BA5h)
}
else if (a == 2)
00007FF6A1831B74 cmp dword ptr [a],2
00007FF6A1831B7B jne main+182h (07FF6A1831B92h)
{
cout << a;
00007FF6A1831B7D mov edx,dword ptr [a]
00007FF6A1831B83 mov rcx,qword ptr [__imp_std::cout (07FF6A1841150h)]
00007FF6A1831B8A call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A18411E0h)]
}
else
00007FF6A1831B90 jmp main+195h (07FF6A1831BA5h)
{
cout << a;
00007FF6A1831B92 mov edx,dword ptr [a]
00007FF6A1831B98 mov rcx,qword ptr [__imp_std::cout (07FF6A1841150h)]
00007FF6A1831B9F call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A18411E0h)]
}
switch (a)
00007FF6A1831BA5 mov eax,dword ptr [a]
00007FF6A1831BAB mov dword ptr [rbp+154h],eax
00007FF6A1831BB1 cmp dword ptr [rbp+154h],1
00007FF6A1831BB8 je main+1B5h (07FF6A1831BC5h)
00007FF6A1831BBA cmp dword ptr [rbp+154h],2
00007FF6A1831BC1 je main+1CAh (07FF6A1831BDAh)
00007FF6A1831BC3 jmp main+1DFh (07FF6A1831BEFh)
{
case 1:
cout << a;
00007FF6A1831BC5 mov edx,dword ptr [a]
00007FF6A1831BCB mov rcx,qword ptr [__imp_std::cout (07FF6A1841150h)]
00007FF6A1831BD2 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A18411E0h)]
break;
00007FF6A1831BD8 jmp main+1F2h (07FF6A1831C02h)
case 2:
cout << a;
00007FF6A1831BDA mov edx,dword ptr [a]
00007FF6A1831BE0 mov rcx,qword ptr [__imp_std::cout (07FF6A1841150h)]
00007FF6A1831BE7 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A18411E0h)]
break;
00007FF6A1831BED jmp main+1F2h (07FF6A1831C02h)
default:
cout << a;
00007FF6A1831BEF mov edx,dword ptr [a]
00007FF6A1831BF5 mov rcx,qword ptr [__imp_std::cout (07FF6A1841150h)]
00007FF6A1831BFC call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6A18411E0h)]
break;
}
从上面的代码可以看出,正常情况下switch和if的性能没什么区别:
- if按顺序执行判断,如果条件满足则执行对应的语句并退出条件判断;如果条件不满足则跳过对应的语句,执行下一个条件判断。
- switch先判断所有条件,如果条件满足则跳转到对应的语句,执行语句,直到遇到break时退出。
其实,编译器实现switch语句有三种方式:逐条件判断、跳转表、二分查找法,具体情况参考C++性能switch语句
枚举定义及作用域
枚举使用时注意作用域的问题:
enum MONTH // 不限定作用域
{
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER
};
const int NOVEMBER = 1; //重定义错误
上面的代码会由于枚举作用域的问题出现重定义错误,参考 C++11 Enum枚举使用心得 使用限定作用域的枚举类型:
enum class DAY // 限定作用域
{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
FRIDAY, SATURDAY, SUNDAY
};
DAY day = DAY::SUNDAY; // OK
int day = DAY::SUNDAY; //错误,限定作用域的枚举类型无法通过隐式转换到其他类型
注:由于枚举成员是const,因此建议用定义枚举成员用大写。
结构体数据耐齐--缺省对齐原则
字节对齐的细节和具体编译器实现相关,参考32、64位编译器各类型大小和字节对齐,一般满足三个准则:
- 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
- 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
- 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
结构体作为数据成员的对齐规则:在一个struct中包含另一个struct,内部struct应该以它的最大数据成员大小的整数倍开始存储。
struct S1
{
char x;
int z;
short y;
};
struct S2
{
char x;
short y;
int z;
};
cout << sizeof(S1) << endl; // 12
cout << sizeof(S2) << endl; // 8
在编码时,可以动态修改字节对齐方式,参考关于字节对齐、设置编译器的内存对齐方式(C++):
Visual C++:
- 使用伪指令#pragma pack (n),编译器将按照n个字节对齐。
- 使用伪指令#pragma pack (),取消自定义字节对齐方式。
g++:
- __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
- attribute ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
函数重载overload与C++Name Mangling
可以使用undname工具查看函数的真实名称,代码如下:
int test(int a)
{
return a;
}
int test(double a)
{
return int(a);
}
使用Notepad++打开编译后的obj文件,搜索“test”得到如下内容:
?test@@YAHH@Z
?test@@YAHN@Z
在cmd中进入undname.exe所在的路径(系统内会有多个undname.exe,根据IDE的情况选择即可),我使用的是下面的路径:
cd C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\Hostx86\x64\
调用undname.exe,输入以下命令:
undname ?test@@YAHH@Z
得到函数的真实名称:
Undecoration of :- "?test@@YAHH@Z"
is :- "int __cdecl test(int)"
同理,可得到:
Undecoration of :- "?test@@YAHN@Z"
is :- "int __cdecl test(double)"
指向函数的指针与返回指针的函数
每一个函数都占用一段内存单元,它们有一个起始地址,指向函数入口地址的指针称为函数指针。
形式:数据类型(*指针变量名)(参数表);
举例:int (*p)(int);
与返回指针的函数之间区别:
- int (*p) (int); //是指针,指向一个函数入口地址
- int* p (int); //是函数,返回的值是一个指针