深度探索C++对象模型-------基本概念

一.名词说明

  • 常量折叠——是在编译时间简单化常量表达式的一个过程。简单来说就是将常量表达式计算求值,并用求得的值来替换表达式,放入常量表。可以算作一种编译优化
  • 常量表达式是指值不会改变,并且在编译过程就能得到计算结果的表达式

二.变量和基本类型

  • 基本内置类型
    C++定义了一套包括算术类型(bool、char、int、long、wchar_t、char16_t、float、long long、double、long double等)和空类型(void)在内的基本数据类型。其中算术类型包含了字符、整型数、布尔值和浮点数
    • 算术类型的宽度
      算术类型中每种类型的宽度在c++中定义了最小的宽度,其实际宽度根编译器有关
    • 类型转换
      类型所能表示的值的范围决定了转换的过程:
      • bool类型转换为其他类型时,如果为true则转为1,如果为false则转为0,其他数据类型转bool时,如果是0则转为false,非0则为true
      • 浮点数赋值给整形,则只保留整数部分
      • 当把整数赋值给浮点类型,如果整数部分超过了浮点数表示的范围则精度可能有损失
      • 给无符号类型赋值,如果超过其表示范围,结果是该类型表示数的总数取模后所得的余数,例如:unsigned int a=0xFFFFFFFF + 2;实际a=1,因为unsigned int表示的数据是0到0xFFFFFFFF,所以其表示数的总数为0xFFFFFFFF+1个,所以余数是1
      • 当我们赋值给一个代符号类型一个超出他表示范围的值时,结果是未定义的。此时程序可能继续工作、可能崩溃,也可能生成垃圾数据
  • 字面值常量
    • 字面值常量表示进制的写法
      直接数字则表示十进制,数字前面加0表示八进制,前面加0x表示十六进制,例如:
      • 20; /十进制/
      • 024 /八进制/
      • 0x14 /十六进制/
    • 字面值类型的说明写法
前缀含义类型
uUnicode16字符char16_t
UUnicode32字符char32_t
u宽字符wchar_t
uUTF-8(仅用于字符串字面常量)char
后缀最小匹配类型
u or Uunsigned
l or Llong
ll or LLlong long
f or Ffloat
l or Llong double
  • 变量
    • 变量的定义和声明
      变量只能被定义一次但可以多次声明,而且
      变量的定义格式:
      int a; //定义并声明
      int a,b,c; //定义并声明
      extern int a; //申明a而非定义
      extern int a =0; //定义而非声明
    • 变量的初始化
      首相声明,变量的初始化与赋值是两个完全不同的概念,初始化不是赋值,初始化的含义就是创建变量时赋予其一个初始值,而赋值的含义就是察除当前值,而以一个新值来替代
      C++语言定义了好几种初始化的形式,下面是几种初始化的方式:
int a = 0;
int a = {0];
int a{0};
int a(0);

花括号初始化被称为列表初始化,无论是初始化还时某些时候为对象赋值,都可以使用这样一组由花括号括起来的初始值。
花括号这种初始化有一个重要特点:用列表初始化时如果存在数据丢失的风险,则编译器会报错,如:int a = 0;char b{a};//存在丢失a信息的可能。

二.关键字解释

  • const限定符
    • const是用来修饰变量及函数的关键字,const修饰的对象表示其是一个常量对象
    • const 修饰的全局常量默认是没有外部链接的,及其他文件是不能使用的,而非const修饰的全局变量默认是具有外部链接的
    • const在头文件中定义则可以用作替换#define功能
    • const在某个区间应用具有安全性,在区间使用时,只是在原来的属性上加了只读属性,所谓只读属性就是const修饰的对象只能使用不能赋值
    • const修饰的变量只能在定义的时候初始化
    • const修饰的对象,在编译的时候把这个常量对象保存到符号表里,符号表中是没有分配内存的,但是要用到内存时会分配,如:
      const int i; extern i;及&i
    • 使用const可以避免编程无意中修改数据的错误,形参使用const可以让函数使用const和非const的实参,使用const引用函数能够正确生成并使用临时变量
    • 指向常量的指针(指向const的指针)
const int *ps32Point;//x是一个指针,它指向常量 const int
int constconst int是一样的

在指针的情况下,赋值时只能以变得赋值给只读的,但不能将只读的赋值给可变的,例如:

int        i = 0;
const int *ps32Point  = &i;//正确,可以变的赋值给不可变的
int       *ps32PointT = ps32Point;//错误,不能将只读的赋值给可变的
  • 常量指针(const指针)
int        i = 0;
int *const ps32Point  = &i;//正确,可以变的赋值给不可变的
int       *ps32PointT = ps32Point;//错误,不能将只读的赋值给可变的
int       *const p;//在C++中,未初始化错误,在C中正确
p = &i;//错误,只能在定义的时候初始化,在C中C++中都错误
const int  m = 0;
ps32Point = &m; //错误,ps32Point是const的,但ps32Point指向int类型,而m是const int类型
ps32PointT = m;//C++中错误,在C中只是个警告,但有的编译器C也是报错
  • define
    • #define 是在预编译中执行的替换工作,预编译结束后宏的符号就消失,程序编译分为四个阶段:
      • 预编译(预处理):展开#define的宏,处理所有的条件预编译指令(如:#if #ifndef #else #endif,处理#include预编译指令,将包含的文件插入到该预编译指令位置),还有#line(既也就是__FILE__,LINE,#ERROR),删除所有的注释,添加行号和文件标识,以便编译时产生错误及警告的行号和标识,保所有的#pragma编译指令,因为编译器在编译时需要
        通常用:gcc -E hello.c -0 hello.i或cpp hello.c > hello.i 进行编译器预处理、、
      • 编译:编译过程就是把预处理完成的文件进行一系列的词法分析、语法分析、语义分析及优化处理后生成相应的汇编代码
        gcc -S hello.i -o hello.s或$/usr/lib/gcc/i486-linux-gnu/4.4/cc1 hello.c 进行编译
      • 汇编:汇编就是将汇编代码转变成机器可执行的指令,每个汇编语句几乎都对应一个机器可执行的指令(其实就是一个翻译过程)
        gcc -c hello.c -o hello.o
        as hello.s -o hello.o
      • 链接:通过调用链接器ld来链接程序运行需要的一大堆目标文件以及所依赖的库文件,最后生成可执行文件
        ld -static hello.o main.o -o hello.exe
  • static
  • 下图是一个可执行程序在内存中的分布
    在这里插入图片描述
    • .text段 : 存放二进制程序,
    • .data段:保存进程已初始化的所有的全局变量(包括静态全局变量和非静态全局变量)
    • .bass段:保存未初始化的全局变量(包括静态全局变量和非静态全局变量)
    • 其他段:杂七杂八的东西暂时不知道
    • 堆区:是通过malloc和free管理(new 和delete)
    • 栈区:主要是存放的是函数的返回地址,局部变量,栈顶的位置等信息
  • 全局static 与全局非static的区别:
    • static 的全局变量实际上与非静态全局变量的存放位置没什么不同,只是被static修饰的全局变量只是在定义它的源文件中有效,其他源文件无法访问
  • 静态局部变量与非静态局部变量有如下几个区别
    • 位置:静态局部变量存放在.data段中(不管初始化或未初始化的),普通的局部变量都是在栈上分配,
    • 权限:虽然静态局部变量是在.data中,在进程的整个生命周期都存在,但是只能在定义它的作用域内被访问,其他文件函数不能访问。
    • 值:静态局部变量如果未初始化则会自动初始化为0,而且每次调用获得的值是上次修改的值。
    • 函数:static修饰的函数是在本文件中可以访问,相当于一个区域限定符
  • extern
    • 在C语言中:
      • 修饰符extern用在变量或函数的声明前,用来说明此变量或函数是用来在次处声明,在别的文件中定义,
    • 在C++中:
      • 全局变量: 在声明全局变量的时候使用extern修饰变量,表明该变量定义于其他翻译单元(既其他文件),如果extern修饰的是一个全局变量的定义则,extern被忽略,全局变量默认是具有外部链接的,但const修饰的全局常量没有外部链接
extern int i  = 0; //这里i是定义非声明,所以extern在这里被忽略
extern int m;      //m是外部定义的,在这里声明
	+ 修饰全局常量:表明该全局常量拥有外部链接(可以被其他翻译单元发现),否则全局常量默认是只有内部链接,即不可被其他翻译单元发现
	修饰全局常量既const修饰的对象,所以在定义全局常量可以被外部访问时必须定义如下:
		```cpp
		extern int c = 10;//可以被其他文件访问的c
		```
	+ 修饰局部变量:表明该局部变量在其他翻译单元中被定义,需要在链接的时候去解析
int test()
{
	extern int i;//说明i在其他编译单元
	extern int i = 0//错误,i的声明周期是函数调用结束,所以这里是错误的
}
	+ 修饰一个字符串:形如extern “C” 之类的用法大家肯定见过了,表明后接的代码块(或者后接的声明)使用C语言调用惯例,如果是用C++代码调用C库,我们也可以使用extern "C++"来编译C库。但是请注意,在extern "C"里面需要严格遵守C规定,函数重载等是不被支持的。
	+ 修饰一个模板:表明该模板已经在其他翻译单元实例化,不需要在这里实例化
  • volatile
    • volatile 告诉编译器不要对该变量进行优化,每次取其值都在其所在的内存取,不是从寄存器去取,其用到的场景有下面三个
      • 中断中,对全局变量,需要加volatile
      • 多任务下对共享的标志需要加volatile
      • 存储器映射的硬件寄存器也需要加volatile,因为寄存器也是随时变化的。
        注意:volateile与const是可以同时使用的,例如只读存储器,虽然软件上他是只读的,但硬件上可以改变其状态。
  • auto
    • 自动存储变量,自动释放内存,函数内部的的默认为auto变量
  • register
    • register用来告诉编译器其修饰的变量将被频繁的使用,如果可能的话,应将其保存在CPU的寄存器中使用,以加快其存储速率。使用register是有条件的,如:
    • register修饰的类型必须是能够被CPU所能接受的,
    • 应为register可能不存在内存中所以不能用&取地址符来取其修饰的变量的地址
    • 只有局部自动变量和形式参数才可以用register修饰
    • 由于寄存器的数量有限不能定义任意多个register修饰的变量
  • reinterpret_cast
    • 通常为运算对象的位模式提供较低层次上的重新编译。如:
int *pc;
char *p = reinterpret_cast<char *>pc;//如果把p当做char的指针用就错了,其实还是int指针,索引这个函数很危险,必须对编译器及类型非常清楚
  • static_cast
    • 适用于将长类型转换为短类型,这个函数是告诉编译器,我们不在乎精度损失,一般长类型赋值给短类型编译会警告,但用这个函数后就不存在了,任何不包含底层const都可以用static_cast函数
  • const_cast
    • 只能改变运算对象的底层const,如const char *p; char *point= const_cast<char *>p;这个我们一般称之为去掉const性质,其常用在函数重载的上下文
  • alignas
    • C++11新增alignas关键字作用,alignas关键字用来设置内存中对齐方式,最小是8字节对齐,可以是16,32,64,128等,alignas相当于gnu c中的__attribute__(aligned(xx)),其是设置对象的地址对其而非对象成员的地址对其,attribute(packed(xx))则其对应的成员按该字节对其
struct Test
{
    char s8M;
    int  s32M;
    short s16M;
};

struct alignas(8) Test1
{
    char s8M;
    int  s32M;
    short s16M;
};

struct alignas(16) Test2
{
    char s8M;
    int  s32M;
    short s16M;
};

struct alignas(32) Test3
{
    char s8M;
    int  s32M;
    short s16M;
};

struct Test4
{
    alignas(8) char s8M;
    alignas(8) int  s32M;
    alignas(8) short s16M;
};

struct Test5
{
    alignas(16) char s8M;
    alignas(16) int  s32M;
    alignas(16) short s16M;
};

struct Test6
{
    alignas(32) char s8M;
    alignas(32) int  s32M;
    alignas(32) short s16M;
};

struct Test7
{
    alignas(8) char s8M;
    alignas(16) int  s32M;
    alignas(32) short s16M;
};
int main(int argc,char *argv[])
{
    Test test;
    Test1 test1;
    Test2 test2;
    Test3 test3;
    Test4 test4;
    Test5 test5;
    Test6 test6;
    Test7 test7;
    printf("test:%ld,test1:%ld,test2:%ld,test3:%ld,test4:%ld,test5:%ld,test6:%ld,test7:%ld\n",
        sizeof(test),sizeof(test1),sizeof(test2),sizeof(test3),sizeof(test4),sizeof(test5),sizeof(test6),sizeof(test7));
    return 0;
}
$ ./a.out 
test:12,test1:16,test2:16,test3:32,test4:24,test5:48,test6:96,test7:64

这里__attribute__(aligned(xx)),相当于Test1、Test2、Test3而__attribute__(packed(xx))相当于Test4、Test5、Test6、Test7

  • alignof
    alignof 获取的是对象的成员中最大对其的值,而sizeof是获取对象占用的内存
int main(int argc,char *argv[])
{
    Test test;
    Test1 test1;
    Test2 test2;
    Test3 test3;
    Test4 test4;
    Test5 test5;
    Test6 test6;
    Test7 test7;
    printf("test:%ld,test1:%ld,test2:%ld,test3:%ld,test4:%ld,test5:%ld,test6:%ld,test7:%ld\n",
        alignof(test),alignof(test1),alignof(test2),alignof(test3),alignof(test4),alignof(test5),alignof(test6),alignof(test7));
    return 0;
}
$./a.out 
test:4,test1:8,test2:16,test3:32,test4:8,test5:16,test6:32,test7:32
  • decltype
    • decltype在C++11标准制定时引入,主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。
    • decltype可以作用于变量、表达式及函数名。
    • 作用于变量直接得到变量的类型;
    • 作用于表达式,结果是左值的表达式得到类型的引用,结果是右值的表达式得到类型;
    • 作用于函数名会得到函数类型,不会自动转换成指针
  • dynamic_cast
    dynamic_cast <type-id> (expression)
    该运算符把expression转换成type-id类型的对象。Type-id 必须是类的指针、类的引用或者void*;
    如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
    将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理, 即会作出一定的判断。
    若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
    若对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用
    【注意】:
    1、dynamic_cast在将父类cast到子类时,父类必须要有虚函数,否则编译器会报错。
    2、 dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
    在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
    在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
  • mutable
    mutalbe的意思是“可变”,和C++中的const相反,在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中
  • static_assert
    用来做编译期间的断言,因此叫做静态断言。
    语法: static_assert(常量表达式,提示字符串)。
    参数描述: 如果第一个参数常量表达式的值为false,会产生一条编译错误,错误位置就是该static_assert语句所在行,
    第二个参数就是错误提示字符串。
    作用:
    使用static_assert,我们可以在编译期间发现更多的错误,用编译器来强制保证一些契约,并帮助我们改善编译信息的可读性,尤其是用于模板的时候。
    static_assert可以用在全局作用域中,命名空间中,类作用域中,函数作用域中,几乎可以不受限制的使用。
    编译器在遇到一个static_assert语句时,通常立刻将其第一个参数作为常量表达式进行演算,
    但如果该常量表达式依赖于某些模板参数,则延迟到模板实例化时再进行演算,这就让检查模板参数成为了可能。
    由于是static_assert编译期间断言,不生成目标代码,因此static_assert不会造成任何运行期性能损失。
  • nullptr
    C++11引入的新特性,nullptr与之前的NULL是一个意思,都是空的意思,但nullptr真正的表示空,而NULL是0的意思,这个在函数重载的调用中就显而易见了。
  • noexcept
    noexcept 用于描述函数不会抛出异常,一旦有异常抛出,会立刻终止程序,它可以阻止异常的传播与扩散。noexcept可以带一个“常量表达式”作为参数,常量表达式为true,表示不会抛出异常,否则代表可以抛出异常
void f2() noexcept(true) //没有常量表达式等同于传了参数true
{
	throw("2");//程序被终止,输出:terminate called after throwing an instance of 'char const*'
}

void f2() noexcept(true) //没有常量表达式等同于传了参数true
{
throw(“2”);//程序被终止,输出:terminate called after throwing an instance of ‘char const*’
}

  • export
    该关键字的作用是实现模板函数的外部调用,类似于正常函数的extern关键字
  • constexpr
    • C++11新标准规定,允许将变量声明为 consterpr 类型以便由编译器来验证变量的值时否是一个常量表达式。声明为constexport的变量一定是一个常量,而且用常量表达式初始化,如果用函数初始化,则函数必须也是被constexport修饰的。
constexpr int mf = 20; //常量表达式
*   指针和consterpr
	在consterpr 声明中如果定义了一个指针,限定符consterpr仅对指针有效,与指针指向的对象无关,
*
  • thread_local
    thread_local变量是C++ 11新引入的一种存储类型,其修饰的类型在每个线程中是静态独立的
  • operator
    operator是C++的重载操作符关键字,其作用就是对操作符重新定义,使其能够适应其他不同数据类型的运算。
  • virtual
    C++中的virtual关键字主要有这样几种使用场景:第一,修饰父类中的函数 ;第二,修饰继承性。注意:友元函数、构造函数、static静态函数不能用virtual关键字修饰。普通成员函数和析构函数可以用virtual关键字修饰

函数

  • C++的可以给函数给定一个默认的参数,函数默认的参数是在函数申明时指定的,函数定义的地方可以不指定,
int fun(int a = 2;
fun();//等价于fun(2)
  • 普通函数
    • 典型的函数由4部分组成:分别是返回类型、函数名、参数列、函数体,但也有例外,如:构造函数、析构函数等
  • 重载函数
    • 函数名相同,但函数的参数个数或参数类型不同,如这种int fun(double a,int b)与int fun(int a,double b)也算是重载。
  • 内联函数(inline)
    • 内联函数的定义:inline int fun(void *);只需要在函数申明及定义前加inline,但我再其他网上看到inline是实现内联的关键字而非申明的关键字,所以要是其为内联则必须放在定义前才有效
    • 优点:
      内联函数是C++为提高程序运行速度所做的一项改进。内联只是为了减少函数调用时的跳转,编译器将根据内联函数的函数体进行是否能够将内联函数的内容假如的调用内联函数的位置来进行编译的,当函数体短小精简时直接将内联函数内容加入调用内联函数的位置处,反之当做函数处理,内联函数减少了调用函数的开销。
    • 缺点(但可忽略):
      由于内联函数将内容加入到每一个调用内联函数的位置,这样将增加内存,但由于内联函数本身短小,可以忽略
  • 虚函数
    简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离
  • 纯虚函数
    纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。纯虚函数也可以叫抽象函数
  • 构造函数
    没有返回类型且函数名与类名相同的函数,创建对象时调用的函数
  • 析沟函数
    没有返回类型且函数名由波浪号(~)加类名构成,销毁对象时调用的函数

类定义是以关键字 class 开头,后面跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表
类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值