const与#define、结构体对齐、函数重载name mangling、new/delete 等
一、bool 类型
逻辑型也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC++中为1个字节。
声明方式:bool result; result=true;
可以当作整数用(true一般为1,false为0)
把其它类型的值转换为布尔值时,非零值转换为true,零值转换为false,注意会发生截断。
二、const 限定符
(1)、用const给字面常量起个名字(标识符),这个标识符就称为标识符常量;因为标识符常量的声明和使用形式很像变量,所以也称常变量。
定义的一般形式:
const 数据类型 常量名=常量值; 数据类型 const 常量名=常量值;
例如: const float PI=3.14159f;
注意事项:
常变量在定义时必须初始化;
常变量初始化之后,不允许再被赋值;
正如我在这里所说,其实加了关键字const只是提示编译器这个变量是常量,如果我们在接下来的操作中试图更改它,编译器会报错,而并不是真正的常量,事实上某些情形下通过指针也是可以更改的(编译器报警告),什么情况下完全不能修改呢,当A是加const限定且初始化的全局变量,此时A位于.rodata段(linux
下)。此外const 用于修饰指针时可以参考这里。
(2)、const 与 #define
const定义的常量与#define定义的符号常量的区别:
const定义的常量有类型,而#define定义的没有类型,编译可以对前者进行类型安全检查,而后者仅仅只是做简单替换
const定义的常量在编译/运行时确定初值,而#define定义的常量是在预编译时进行替换,不分配内存。
作用域不同,const定义的常变量的作用域为该变量的作用域范围。而#define定义的常量作用域为它的定义点到程序结束,当然也可以在某个地方用#undef取消
#define定义的常量,容易产生副作用:
//Effective C++ 3rd的一个例子。
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
int a = 5;
int b = 0;
CALL_WITH_MAX(++a, b); //a被累加二次
CALL_WITH_MAX(++a, b+10); //a被累加一次
在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”
此外,定义常量还可以用enum,在c++ 中尽量用const、enum替换#define定义常量,用inline 替换带参数的宏定义;但 #define 在底层编程中是必不可少的,下面举个例子:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include<iostream>
using namespace std; #define STR(a) #a int main(void) return 0; |
如果是完全的c++
菜鸟,这里还得稍微解释一下细节,iostream 是c++标准库的一个io流头文件,跟C语言不太一样的是一般没有.h 后缀,using
namespace 表示命名空间,简单理解就是统一的函数前缀,类比pthread库如pthread_mutex_init,
pthread_mutex_lock 用c++ 方式来表示可能是 pthread::mutex::lock。
cout是输出流对象,<<操作符在C语言中是左移位运算操作符,在这里被重载成输出操作符,之所以能并列输出是因为如cout<<xxx 返回的是cout 的引用,以后还会再提。参数宏定义的意义就很清楚了,查看下输出即可。
我们知道printf函数带有可变参数,函数式宏定义也可以带可变参数,同样是在参数列表中用...表示可变参数。例如:
1
2 3 4 5 6 |
#define showlist(...) printf(#__VA_ARGS__)
#define report(test, ...) ((test)?printf(#test):\ printf(__VA_ARGS__)) showlist(The first, second, and third items.); |
1
2 |
printf("The first, second, and third items.");
((x > y) ? printf("x>y") : printf("x is %d but y is %d", x, y)); |
在宏定义中,可变参数的部分用__VA_ARGS__表示,实参中对应...的几个参数可以看成一个参数替换到宏定义中__VA_ARGS__所在的地方。
(三)、结构体对齐,初始化方式
什么是内存对齐
编译器为每个“数据单元”按排在某个合适的位置上。
C、C++语言非常灵活,它允许你干涉“内存对齐”
为什么要对齐
性能原因:在对齐的地址上访问数据快。
如何对齐
第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragma pack(n)所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个min中最大值的整数倍。
举个例子,
struct test
{
char a;
double b;
char c;
};
根据规则1,a
在位置0;根据规则2,因为VC默认pack为8,可以通过项目-》属性-》c/c++
-》代码生成-》结构体成员对齐选项修改,也可以使用#pragma pack(n) 来修改,#pragma pack() 取消修改,那么b
占据8~15;根据规则2,c在16;现在总共17个字节,根据规则3,结构体总大小需对齐到8的整数倍,即总共是24个字节。
如果将pack 修改为4,则总大小为16。在VC上pack 共有1,2,4,8,16 等5种选择,而linux g++ 则只有1,2,4 可选,默认是4。
推荐的的初始化方法应该是(当然,一个成员一个成员地初始化也行):
struct s
{
int l;
char* p;
};
struct s s1 = {.l=4, .p = "abcd"};
struct s s2 = {l:4, p:"abcd"};
不建议直接写死如 struct s s1 = {4,
"abcd"}; 原因在于你对结构的内存布局作了假设。如果这个结构是第三方提供的,他很可能调整结构中成员的相对位置。而这样的调整往往不会在文档中说明,你自然很少去关注。如果调整的两个成员具有相同数据类型,编译时不会有任何警告,而程序的逻辑上可能相距十万八千里了。
(四)、域运算符
C++中增加的作用域标识符 ::
用于对与局部变量同名的全局变量进行访问
用于表示类的静态成员,以后讲到类的时候再详谈
(五)、new、delete 运算符
(1)、new operator
new运算符可以用于创建堆空间,成功返回首地址,失败抛出异常
语法:
指针变量=new 数据类型(值);
指针变量=new 数据类型[长度n];
例如:
int *p; p=new int(3);
char *pStr=new char[50];
(2)、delete operator
delete运算符 可以用于释放堆空间
语法:
delete 指针变量;
delete [] 指针变量;
例如:
delete p;
delete [] pStr; // 类似 delete pStr[0] , delete pStr[1], ....
(3)、new 和 delete 执行的步骤
new operator
内存分配(operator new),类似malloc
调用构造函数,讲到类再说
delete operator
调用析构函数,讲到类再说
释放内存(operator delete),类似free
实际上new 有三种用法,包括operator new、new operator、placement new,new operator 包含operator new,而placement new 则没有内存分配而是直接调用构造函数,具体的差异以后再谈。
(六)、函数重载、name managling 与extern "C"
(1)、函数重载
相同的作用域,如果两个函数名称相同,而参数不同,我们把它们称为重载overload,函数重载又称为函数的多态性(静态)
函数重载不同形式:
形参数量不同
形参类型不同
形参的顺序不同
形参数量和形参类型都不同
调用重载函数时,编译器通过检查实际参数的个数、类型和顺序来确定相应的被调用函数。
函数的重载跟函数的覆盖、函数的隐藏是不同的,这一点以后再讲。
合法的重载例子:
int abs(int i); long abs(long l);double abs(double d);
非法的重载例子:
int abs(int i);
long abs(int i); void abs(int i);
//如果返回类型不同而函数名相同、形参也相同,则是不合法的,编译器会报"语法错误"。
(2)、name mangling 与extern "C"
name managling这里把它翻译为名字改编,C++为了支持函数重载,需要将函数名根据参数的不同进行name managling以便区分。
extern “C” 可以实现C与C++混合编程,被extern "C" 修饰的变量和函数是按照C语言方式进行编译和链接的,即对C语言写的函数不进行改名,一般在
C的头文件中使用,如果头文件被C代码包含并用C编译器编译,则__cplusplus 没有定义,extern “C" 被略过,如果头文件被C++代码包含并被C++编
译器编译,存在__cplusplus 定义故extern "c" 提示编译器不要对 {} 内函数进行改名。
#ifdef __cpluscplus
extern “C”
{
#endif
...
#ifdef __cpluscplus
}
#endif
实际上,编译器对数据成员也会进行name mangling处理,目地是区分派生类和基类中可能的同名成员。
不同C++编译器的name mangling 方案是不同的,这是造成不同编译器之间存在二进制连接兼容性的主要原因之一。
(七)、带默认形参值的函数
函数声明或者定义的时候,可以给形参赋一些默认值,调用函数时,若没有给出实参,则按指定的默认值进行工作。
* 函数没有声明时,在函数定义中指定形参的默认值
* 函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值
* 默认值的定义必须遵守从右到左的顺序,如果某个形参没有默认值,则它左边的参数就不能有默认值。
void func1(int a, double b=4.5, int c=3); //合法
void func1(int a=1, double b, int c=3); //不合法
* 函数调用时,实参与形参按从左到右的顺序进行匹配
* 重载的函数中如果形参带有默认值时,可能产生二义性
1
2 3 4 5 6 7 8 |
int add(int x = 5, int y = 6);
int add(int x = 5, int y = 6, int z = 7); int main(void) { int sum; sum = add(10, 20); return 0; } |
sum=add(10,20)语句产生二义性ambiguity,可以认为该语句是调用第一个函数,也可以是第二个,因此编译器不能确定调用的是哪一个函数。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范
const与#define、结构体对齐、函数重载name mangling、new/delete 等的更多相关文章
- 函数定义从零开始学C++之从C到C++(一):const与#define、结构体对齐、函数重载name mangling、new/delete 等
今天一直在学习函数定义之类的问题,下午正好有机会和大家共享一下. 一.bool 类型 逻辑型也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC+ ...
- C++学习之路(一):const与define,结构体对齐,new/delete
前言:针对C++ Primer和Effective C++两本书,以及技术博客与实验测试,本系列主要是针对C++进行系统化学习,记录学习中遇到的问题和经验. (一)const与define 关于con ...
- 【C++】结构体/结构体数组/结构体指针/结构体嵌套/函数参数/const
一.结构体声明 struct Student { //成员列表 string name; int age; int score; }; //s3;定义时直接声明 int main() { struct ...
- C语言结构体对齐
1.结构体变量中的元素如何访问? (1)数组中元素的访问方式:表面上有2种方式(数组下标方式和指针方式):实质上都是指针方式访问.(2)结构体变量中的元素访问方式:只有一种,用.或者->的方式来 ...
- C语言中结构体对齐问题
C语言中结构体对齐问题 收藏 关于C语言中的结构体对齐问题 1,比如: struct{short a1;short a2;short a3;}A;struct{long a1;short a2;}B; ...
- C语言提高 (5) 第五天 结构体,结构体对齐 文件
1昨日回顾 2作业讲解 3 结构体的基本定义 //1 struct teacher { int id; char name[64]; }; struct teacher t5 = { 5, " ...
- C语言基础--结构体对齐,位域,联合体
结构体对齐 1--结构体对齐的原因与意义 许多计算机系统对基本数据类型的可允许地址做出了一些限制,要求某种类型的对象的地址必须是某个值K(通常是2,4,8)的倍数,而这个k则被称为该数据类型的对齐模数 ...
- 解析C语言结构体对齐(内存对齐问题)
C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...
- 4-17疑难点 c语言之【结构体对齐】
今天学习了结构体这一章节,了解到了结构体在分配内存的时候采取的是对齐的方式 例如: #include<stdio.h> struct test1 { int a; char b; shor ...
随机推荐
- Jquery实战——横纵向的菜单
横纵向的菜单效果,点击纵向菜单显示其子菜单.鼠标指向横菜单的时候.显示其子菜单,鼠标离开,子菜单隐藏. HTML代码: <span style="font-size:18px;&quo ...
- SpringBoot添加支持CORS跨域访问
原文:https://www.jianshu.com/p/c6ea21b64f6e CORS(Cross-Origin Resource Sharing)"跨域资源共享",是一个W ...
- SpringMVC杂记(1) 使用阿里巴巴的fastjson
国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...
- python的threading.Thread线程的start、run、join、setDaemon
Pycharm整体看下Thread类的内容:模拟的是Java的线程模型 表示方法method,上面的锁头表示这个是类内部的方法,从方法名字命名规范可以看出,都是_和__开头的,一个下划线表示是子类可以 ...
- IIS7的CMD指令
AppCmd.exe工具所在目录 C:\windows\sytstem32\inetsrv\目录下 使用命令行管理IIS 7.0时,需要使用IIS7.0提供的全新管理工具AppCmd.exe.AppC ...
- QT 异步函数转为同步函数的方法
在QT中,一般推荐使用异步函数.除了异步函数的非阻塞特性外,QT的Signal/Slot特性在异步函数中可以得到充分的发挥.因此,在QT中,很多API的设计都是使用非阻塞的异步函数作为API,然后执行 ...
- 怎么删除桌面右键"打开好桌道壁纸"
“好桌道”是一款优秀的桌面美化工具,其中的子程序“好桌道壁纸”是其重要的组成部分,但是在卸载其子程序“好桌道壁纸”时,往往会在桌面的鼠标右键中残留下“打开好桌道壁纸”项,下面解密通过修改注册表的方式彻 ...
- 采用web service传输超大数据
因为以前也没有做过相关的web service开发,对于Xfire也只是知道有这么一个框架.当然现在它已经变成apache基金会旗下的一个开源项目CXF.不过,现在依旧有很多公司还在用Xfire作we ...
- .net 序列化与反序列化
1.序列化 反序列化 C#中如果需要:将一个结构很复杂的类的对象存储起来,或者通过网路传输到远程的客户端程序中去,这时就需要用到序列化,反序列化(Serialization & Deseria ...
- java学习笔记2--数据类型、数组
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note2.html,转载请注明源地址. 1.数据类型 Java数据类型有: 原始数据类型(Pr ...