
- 内存对齐
- alignof测的是什么
- 调大与调小
- 调大 \_\_attribute__((aligned(n)))
- 调小 #pragma pack()
- 当调小成1时
- C语言中强制类型转换的本质
- typeid
- 静态类型与动态类型
- typeid的真正大用是在动态类型中
- 4种cast转换
- static_cast的强制类型转换
- reinterpret_cast的强制类型转换
- const_cast的强制类型转换
- dynamic_cast的强制类型转换
- const关键字
- C中const的用法
- C++中const的用法
- 用法一
- 用法二
- const的本质
- 与const有关的另外几个关键字
- mutable
- constexpr
- constinit和consteval
- exception类
- noexcept关键字(C++11引入)
alignof测的是结构体中你定义的变量的最大的那个变量类型所占的字节的大小,这个是错误的,这个没有看到本质。其实本质这个alignof看的是你按多少个字节对齐。所以由这点你也可以推断出来,其实,你在结构体中写了double后,alignof测出来是8,那么其实它就是按8字节去对齐的,而不是按4字节对齐的,所以可以得出结论,这个不是死的,不一定一直都是4,只有结构体里面最大的那个是int的时候才是4,而如果结构体里面最大的那个是double,那么就是8个字节了,而事实上,这个不仅不固定,而且还是手动可调的,下面就讲了如何手动调。
调大与调小这个有关C加加的内存对齐,嗯,它这个里面就是首先它有一个a line的这个东西就是他跟size of,一般这个课程里面是拿了a line跟size of一起去用,一起去对比这个aline的话,它就是用来查看你这个这个结构体的,对其的最小的一个单元,就是你设置好的结构提的一个对齐的最小单元。然后size of就它整个结构体的大小嘛,然后你一般是用aline去看的,如果你没有,然后这个结构体内内存的最最对齐的最小单元,你是可以调的,你往大了调,你就要你就可以用一个那个aline的那个a line a of什么的一个东西,然后你去调,然后给它可以给它设置的。
然后你往小了调,可以用一个parent,然后什么packed,然后往小了调,把它那个结,那个结构体的那个最小,对其单元往小了调,就是意思就是说,嗯,它有一个可以查询你结构体对齐的一个最小单元的一个东西,叫line的line of,然后你可以把你可以人为的把它结构体内存对齐,往大了调,往小了调往大了调,你就用一个那个东西往小了调,你用那个pad。
调大 __attribute__((aligned(n)))调完之后,你在用alignof去测,就会发现得到是最大的那个是你设置的那个大的了,比如之前最大的是8,你手动设置成32了,那就变成32了。
就是你往大了调,就是用那个attribute,下划线是用了下划线,下划线,Attribute,然后括号,Aline的,然后里面你写你要写的那个,往大调调到多大,然后就你对齐的那个最小单元往大了调调多大,然后就是这样。
这个往小了调,也会把alignof测的那个值给改变了
然后你往小了调,就是用那个一个井号,然后一个paragraph pragma peck,然后括号往然后里面填数字,一般是上面有一个井号调调,完数字之后下面再写一个井号,然后把那个东西置空,里面括号变空,这样的话,你下面的那个就不会变,不会变,不会也受到你的影响,只有上面中间夹住的那部分受到影响。
他这个往小了调,就是有个夹层,你你上面一个井号,下面井号,上面井号写的你要调大小,最小多调多少,你下面那个井号就给他一个空白,就是夹到这里,然后,这里面的东西就是调的,这里面几个题,就是你要调的,在这里面之外的其他的几个题就不受这个影响。
当调小成1时当调小成1时,你会发现sizeof也变了,直接就是有多少个字节,就是多少个字节了,这个情况其实就是按1字节对齐了,所以内存不存在空出来的情况了。
这里我们试了之后往大调,要用那个才行,然后往小的调用那个才行。然后这个的话不一定是绝对的,可能是我们没有注意到某一些细节的语法,说不定改一改他们也可以,大的可以往小调,小的也可以往大调,但这里就不深究了。
再往小了调的话,是上面那个井号填数字下面那个井号填空就是夹层嘛,它上面有个井号,下面有个井号都写了一个pack parent的pack,然后上面的井号是填数字,你要设定的数字,然后下面的井号就是空,下面井号就不用设置的是这样子的,下面井号的数括号里面是空的,不用设置数字,就上面那个井号,你的那个里面的package的括号里面采用设置你要设置的大小。
内存对齐,还有一种往上调的方法,就是用那aline as aline as的话,它是写在那个struct定义的那个,那那句话里面的,你比如说你是struct什么什么什么是吧,然后加个变量名是吧,然后你那个aline as加个括号,写上你要定义的往大了调的那个对齐数,它是写到struck的后面,紧挨着后面写的,然后它这个的话也是只能往大了调。
C语言中强制类型转换的本质补充记一下关于C语言里面的一个强制类型转换的一个经典题目,就是他一会儿是那个类型,然后相加到右转那个类型的时候,这个这个东西是怎么处理的。从最深层的原理上来讲,你要理解就是强制类型转换的本质,在我的理解是复制,就是他,如果你要把一个硬盘类型的强制类型转换成其他类型的,其实他应可类型,它那个变量本身是不会变的,他是把它复制的,另一复制了一份放到另外一个内存空间里面。然后用的是另外那个内存空间的里面东西,然后去给了那个强制类型转换的那个值,就是说,它是有一个复制的复制的一个过程的,所以它后面你一些题目如果是加减,啊加完之后再去强转也是的。
也是的,就是你要先是相加完,相加完后得到那个值,你发现你要强转了,那你就把你相加后得到那个值,它其实在内存里面是复制到另外一个内存单元,然后把那另外付到另外内存单元那个值再付给那个,你要强转那个职是这样子的。所以他你这个你要理解这个本质就是你要深层次去理解它,强制类型转化其实是一个复制了一个,他把他那个值复制了一份,到另外一个内存单元里面去了,然后用的是另外一个内存单元里面的东西,它本身它那个。这个值它是没有变,它类型是没有变的,它只是用的是不是它的那个值,而是它复制之后的那个,那个内存单元里面的那个值,其实也不是叫复制,就说重新开辟了一个内存单元,然后用就放了一个值,这个值就是你要强转那个值,也就是说它用的不是它本来的那个值,那个值是不会变的,还是那个类型的,反正总结你可以大致一些,就是复制了。
反正总结你可以大致理解,就是强制类型转换,它本次用的不是之前的值,而是他重新理解为你可以理解为复制到另外一个内存单元里面用的是那个纸,然后你无论是加加减减加加,最后,最后你要强转的时候都是最后他先把你加到几点,加得到那个值之后,复制到那个内存单元,那个内存单元地址,你再去拿去拿那个去用是这样子的,但是它这个跟船舱的形参跟行参传传参那个东西。还是有一点点小小区别的,也没有必要把他们这两个放到一起去理解,你就分开理解,但是你这个这个东西强制类型的话,真的是他把他那个值经过处理,然后把他得到处理之后,得到那个值放到另外一个内存单元里面的,用的是那个,另用的是另外那个内存单元里面的值,你可以最终核心你要理解这一点,后面的那些题目就比较容易理解了。
typeid有一个东西叫太拍的,太拍的是用来查它,那个你的这个变量的类型的,就是比如说你的int类型的变量,它的这个里面用了type ID,他是个类类的type ID,你可以把那个变量放到里面去,然后点面去查他这个这个类里面的名字,那么,如果你是in的type ID给你返回来就是I,然后这里还有个unsigned跟赛的一个区别,比如说你如果是差类型的type ID给你返回的是C,然后你如果是an cy X类型的,他type拍的给你返回来是H。然后如果你是的child类型的,那么它type ID给你返回来是一个a,就是说,它其实这个东西深层次挖这个type ID是很有用的一个东西,它跟编译器有关的,它底层是跟编译器有关,就是我们平常编译时候,它编译器到底是怎么判断我们的类型的呢?它其实用的就是的ID,类似这样的东西。
就是他平常平常编译器判断我们的类型,校验帮我们进行校验,然后报错什么的,就是用了type ID type ID类,用了type ID类似的样东西,它把它这个变量它有一个名字,有用一个字符串,这字符串可能是一个字符,也有可能是两三个字符,通过这个去对比,这底层原理就是这样子,而这个type ID是个非常有用的东西,就是它的深层次的说明是这样子的。
静态类型与动态类型就其实它这个那个编辑器啊,帮你做那个就是一个参数类型的一个,就是类型的一个校验的时候,它有分为一个静态的,一个静态类型,还有一个动态类型。静态类型,简单说,就是在编译的时候出现的动态类型,就是说在运行的时候出现的,就说他你如果在编译的时候就出现错误了,或者编译的时候就已经确定你是哪个类型了,那么这个静态类型,比如说你之前写的啊。然后这些char这些,然后你如果是在运行的时候,你才知道这个东西的类型,那就是动态类型,比如说你之前用了void void,它编译的时候,它是不知道它的类型的,就运行的时候它才知道它的类型,比如说运行的时候,你发现你给它用的就是int,那么这个Y代表的就是int,然后运行的时候,你如果用运行的时候,你发现你给的它是char,那么这个Y就代表char。
然后这个动态类型就是你运行的时候,他经常会出现崩溃,就就是你可能如果你在某个情况下,它的类型不匹配了,然后你在运行的时候,你你你这个是个动态类型的一个东西,你运行的时候他们不匹配,那就崩溃了,所以这个程序就这样子就没有了,见状性见状性就差了点,然后你就可以通过这台拍的,你在它运行的时候就是动态类型吗,你在它运行的时候。然后你经常的抽出来,然后拿他的那个参数啊,变量的类型校验一下,然后发现他是不是匹配,如果不匹配的话,就做下一步的 *** 作,然后如果匹配的话,就好不匹配的话,就是就是程序,就做下一步 *** 作就处理处理一下怎么样的,这样的话就提高了一个程序的健壮性,就你在时不时的抽出一点东西来,然后拿拿他的那个那个钛白的去匹配。
然后我刚刚说了这点还是有一点点出入,就是说他可能不是进行一个匹配的问题,是它运行的时候,它的变量的类型,如果是这样,他可能会往内部去执行,如果他这个变量类型是那样的话,他可能会往那个方向去执行,那么这个时候呢,你你就要需要用一个东西来去查它的这个变量类型是什么,查出来之后,他如果是那样就那样走,如果是这样就这样走,这样的话,就是就不会说你本来是这个类型的,但是你却走了那个方向。这样的话,程序就会运行时就会出现一个问题,就会崩溃,就就可以解决这个问题。你可以通过一个方式,就是白ID方式得到它的这个类型的一个特点,然后去判断,从而判断你该怎么去走,这个就是一个提高程序健壮性的一个方法。
typeid的真正大用是在动态类型中所以总结来讲,这个type ID,它最真正的用法是用在这个运行时的时候,这个动态类型的一个检查上面的。
这太拍的呢,既可以返回动态类型,也可以返回静态类型,然后你如果是你,如果他这类型的静态跟动态的时候都是一样的,那它就返回静态类型,如果她动态类型和静态类型不一样,那它就返回动态类型,然后他这个太白应该真正显示用处的地方,就是在引入了后面的东西才有用呢,这里我们就大概了解一下就行,就是大概了解一个这样概念就行,后面的话等他真正派上大用的时候再去看。
就是说这个type ID真正派上大用场了,是在动态类型里面派上了大用场。然后,但是我们现在目前还没有接触到动态类型,大部分都是在静态类型里面的,所以目前还没有体会到它的用处,等到后面接触到一些动态、动态的时候,它就会有大用。
4种cast转换 static_cast的强制类型转换跟C里面的强制类型转换一样
只不过写的时候变成了
int *p2 = static_cast<int *>(p1);
这个static cast总结来讲用的并不多,但是你要认得他就是C加加里面一个强制类型转换,但是他这个强制类型转换是只能转安全的,就是比如说你是in te,你就只能把它转成in te就是这样子,如果你本身是差,你要用这个强转转不了的,在C加加,它比那个CC的一个类型校验更严格,就是你如果是在C加加里面,你强制类型转换是不行的,你用一个static cast把一个int转化成差,他是不给你的。会报错,甚至你在C加加里面,你用C的那种强制类型转换的方式,直接来个括号,然后里面写上类型去强转,它也会报错,就是C加加它比C要严格一点,它对你束缚多一点,它C的话呢,是完全是放任你自由的,这是一个C加加强制类型转换的特点,它不给你强转。
刚刚说错了,它这个C语言里面那个强转直接加括号,然后来一个超星或者是什么强转是完全可以转过去的,C加加里面也是允许的,它唯一不允许的是你用static cast这种方式强转,这种方式强转是不行的,但是C里面那种强转在C加加里面是可以编译,是可以通过的。
总的来说又是一点,你写成static cast就不能强转,你写成C的那种,直接来个括号就可以强转在C加加里面是这样子的。
reinterpret_cast的强制类型转换这种类型的强制类型转换就是可以强制类型转换的
在C中这样写
unsigned int *p = (unsigned int *)(0x530000e0);
在C++中使用reinterpret_cast写
unsigned int *p = reinterpret<unsigned int *>(0x530000e0);
这个inter interpret cast就是让编译器完全放弃你的这个类型检查,就是告诉编译器要放心,我这边是可以的,你完全放弃类型检查就行了,我这边是可以把关的,就是这个意思。
在C加加里面关于cos是这样子,你如果你定义这个变量是cos的变量,那么你如果要用个指针指向它,那么你的这个指针必须也是定义成cos的才能够用的。
如果你定义的那个指针没有带cos的,你去指向那个cos的变量的话,它是无法成功的,因为你一个是带cos的,一个是不带cos的,这是不能指向成功的。
在这里回顾一下cos,其实cos的就是一个警告牌而已,就他并没有在物理上面使它这个数变成可读可写,使它这个变量变成不可读,不可写,它只是给你个警告牌,就说它本质上还是可读可写的,是这样子的,它只是给警告一下,在编译阶段说你既然定义了cos的,那么就警告你一下,这个不能用,但是你这个实际上是可以用,可以改的。
所以本质上是编译器把你这个拦截掉了。
就是这个有一个问题,就是你会发现你用这个方法去改,他地址里面的值,然后你打印出来之后,你会发现a和新批是不一样的,你a还是之前的那个五,但是新批已经改成了14了,然后你会觉得很奇怪,那么我们就可以通过一个方法去看,就是我们去打印出那个a的地址,还有新批的地址。不是新P的地址,就是P的地址,就是你通过打印出那个a的地址和P的地址去看一下他们到底指向的是不是同一个地址,你因为你可能怀疑他有没有中间做一个中间变量啊,或者用一个做了一个影视转换,然后他们这两个地址是不一样的呢?是不是,所以你要去看一下他地址是不是一样的。
然后你最后发现a的地址跟批的地址是竟然是一样的,但是a的地址跟P的地址是一样的,但是a的值却没有变,他还是五并没有改,改成14,这个就是一个很奇怪的东西,它中间并没有做任何的一个中间变化,也没有说提前复制啊,一个值到另外一个地址去,他地址是一样的。所以这里就是其实是C加加的一个优化来着,是C加加的编译器做了一个优化,就是说它你可以理解为它很像一个红。就是你用cost去定义a等于五的时候,很像去用一个宏去定义了a等于五,那么这个时候它的编译的时候呢,就已经就说他这个a,他就已经跟五是已经绑定起来了,跟红一样,那么你在编译的时候呢,他就已经用这个五把a给替代掉了,就是类,类似于这样。
所以说你后面无论是如何怎么样,你只要碰到这个a的话,那它自动就会把它变成五,他跟五个绑定起来了,但是实际上a的值,它在运行的时候已经通过你的指针去改变了a的值其实已经是他的那地址面积已经是变成了14了,就是你那个批跟a是一个地址,你通过改变pi就就把那地址面值给改改成14了,那那个是运行时候的,你是无法左右他滨一时候的那个变的,他的那个空是a,你可以理解为它就是一个红了,她在编译的时候就已经把那个a用五给替代掉了,所以你打印出来a就是还是之前的五。
const_cast的强制类型转换int main(void)
{
const int a = 5;
int *p = (int *)&a;
*p = 14;
cout << "a = " << a << endl;
cout << "*p = " << *p << endl;
return 0;
}
本来以为通过这种强制类型转换的方式,简单说就是把之前原先定义的const int给强制类型转换成int,然后再去改它的值,就可以把const类型的a的值给改掉了,但是最后发现虽然*p已经改成14了,但是a还是等于5,
上面那个是老式的C的写法,C++中的写法可以写成const_cast这样
int main(void)
{
const int a = 5;
int *p = const_cast<int *>&a;
*p = 14;
cout << "a = " << a << endl;
cout << "*p = " << *p << endl;
return 0;
}
这两种写法的效果是一样的,但是新式的写法更好,因为这样可以让人清楚的知道,你是有意这么做的,而不是不小心搞错了类型的。
//int strcmp(const char *p1, const char *p2);
char a[] = "dfdfdf";
char *pa = &a;
strcmp(const_cast<const char *>(pa), const_cast<const char *>(pb));
cost cast的用法,主要是用在drink compare那里,比如说你drink compare里面你传的参数,它要求是cost的类型,但是很多时候你要传进去的那个参数,它并不是一个cos的类型,那你就要用costcast把你要传进去的那个参数先全转成cost的类型,然后再去传。
dynamic_cast的强制类型转换前面三个都是编译时起作用,这个是在运行时起作用,这个涉及到后面的东西,所以后面再细看
const关键字 C中const的用法const int *p;
int const *p;
这个两个是一样的,都是说指针指向的那个值是常量,而指针可以变,这种用法比较常见
int * const p;
这种是指那个指针是一个常量,这个指针指向的值可以变,这个本质上其实就是C++的引用,所以这种用法在C++中就很少用到了,因为一般要用,就直接用C++中的引用了
const int * const p;
int const * const p;
这个就是综合了前面两种,指针和指针所指向的变量都不能变,都是常量。这种用法不常见
C++中const的用法C++里面多了两个const的用法
用法一第一个就是在引用前面加const,然后去传参
发现一个细节,就是引用的话,什么都不用管,就是你在定义的时候给变量加一个&就可以了,其他的就按照正常变量来写,注意,一定要把引用和指针的写法完全分隔开,他们的写法完全不一样,不要搞混了
之前在输入输出型参数中有讲过,传参的时候,加了const就是说明是输入型参数,只是传进去不用改的,在C语言中传参就是这样
int fun1(const int *a)
{
if(*a > 100)
return 0;
else
return -1;
}
而在C++中你可以理解为这里它更倾向于使用引用,而使用引用可以完成上面的这个功能,而且在完成上面那个功能的同时,还多了一个功能,就是把指针也给变成常量了,而不单单是指针所指向的变量,这样就更明确地告诉了编译器,我这个就是输入型参数
int fun2(const int &a)
{
if(a > 100)
return 0;
else
return -1;
}
仔细去品这两个中指针和引用的区别,还有要有一个意识,就是意识到传参里面写的东西其实就是一个定义,所以上面那个是指针的定义,a就是定义了一个指针,那么用的时候,取里面的值就要加*p,而下面那个是引用的定义,引用的定义是C++经过封装了的,所以直接对应的就是那个变量了,很方便的,所以a直接就是那个变量了。所以综上,指针和引用的用法是完完全全不一样的,要完完全全分隔开来看
用法二第二个就是const成员函数
struct A
{
public:
int i;
int j;
int func10(void) const;
};
int A::func10(void) const
{
this->i = 5;
}
你在这个成员函数的后面加上const,就说明这个成员函数里面是不会修改成员变量的值的,比如这里,是不会在这个func10中去修改这个成员变量i和j的,向上面那样写就会报错,因为你在成员函数里改了成员变量的值
const的本质本质上是编译器在帮你保护,实际上物理上并没有设置成只读,只是在编译器层面帮你做了一个检查而已,因此后面想要强行改也是可以的。
与const有关的另外几个关键字 mutablemutable是用来突破const成员函数的限制的,就是你可以单点地突破,选择一两个特殊的成员变量,设置为可以改变的。
constexpr这个本质上是一种优化,就是利用编译时的计算能力,增加运行时的效率。你加了这个关键字就意味着你允许编译器去优化,比如你要算一个乘,那就不用在运行时再算了,而是在编译时就帮你算好了
constexpr int multiply(int x, int y)
{
return x * y;
}
const int val = multiply( 10 , 10 ); //在编译时就会计算了
constinit和consteval
这两个是C++20才出的,和constexpr差不多,都是为了提高效率用的
exception类标准库统一定义的异常抛出,便于抛出异常和抓住异常的两个人的统一,他是一个基类(父类),很多派生类都用到了它作为父类
当你没有写throw列表时,就代表该函数可能会抛出任何一种形式的异常。不过i一般不建议这样用,这样用模棱两可,如果你知道要抛出异常,那就用throw列表写出要抛出异常的类型,如果你知道没有异常,那你就写noexcept,否则代码写得就很烂。其实这本质上是写代码的人和编译器和看代码的人的一种交流方式
noexcept关键字(C++11引入)本质是对throw()这种方法的抛弃,用来替代throw(),当函数不会排除任何异常时,就不用用throw(),而是用noexcept了
noexcept(bool);
括号里面是一个表达式,如果是true,就表示不会排除异常,如果是false,就表示会抛出异常,所以当看到这样一个函数就放心大胆用,绝对不会抛出异常。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)