printf()函数计算参数是从右向左入栈的。
*(ptr++)+=123;含义为*ptr=*ptr+123;ptr++;//括号的优先级比*低
float a=1.0f;
cout<<(int&)a<<endl;
//浮点数在内存里和整数的存储方式不同,(int&)a相当于将浮点数地址开始的sizeof(int)个字节当作int型数据输出
//float a=1.0f在内存中的表示都为3f800000
//即输出1065353216
static关键字
隐藏 对于变量或函数,在一个源文件被定义,不能在其他源文件调用。
保持变量内容的持久 修饰局部变量,因为存储在静态存储区,所以初始化一次,下一次运算依据上一次的结果值,作用于该模块内,与源程序生命周期一样。
C++中的类成员声明static 在程序中只有一份复制品,静态成员属于类,不属于对象,不能使用this指针调用
不推荐在头文件中定义静态变量。
const关键字
1.const 修饰成员变量
告诉编译器某值是保持不变的
2.const修饰指针变量时:
(1)只有一个const,如果const位于*左侧,表示指针所指数据是常量,不能通过解引用修改该数据;指针本身是变量,可以指向其他的内存单元。
(2)只有一个const,如果const位于*右侧,表示指针本身是常量,不能指向其他内存地址;指针所指的数据可以通过解引用修改。
(3)两个const,*左右各一个,表示指针和指针所指数据都不能修改。
3.const修饰成员函数
(1)const修饰的成员函数不能修改任何的成员变量(mutable修饰的变量除外)
(2)const成员函数不能调用非onst成员函数,因为非const成员函数可以会修改成员变量
4.const修饰函数返回值(返回值不能是局部变量)
(1)指针传递
如果返回const data,non-const pointer,返回值也必须赋给const data,non-const pointer。因为指针指向的数据是常量不能修改
(2)值传递
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值
5.常引用类型
只访问,不修改数值
volatile关键字
修饰被多线程访问和修改的变量,变量被修饰后,系统调用时便会去寄存器中提取,而不是去缓存区调用
assert()捕捉错误
assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行
main()的形参
int argc表示命令行参数个数,char* argv[]中的argv[0]表示带有main()函数的源文件名,argv[1]及往后参数均为实参命令
c++中所有动作不是均由main()引起,静态变量、全局变量、全局对象的分配在mian()之前完成。全局对象在main()完成之后析构
*p++表示先进行取值操作,然后对指针执行++操作
(*p)++表示先进行取值操作,然后对该值进行++
前置自增(++i)比后置自增(i++)效率更高
new/delete与malloc/free区别
a.属性
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持c。
b.参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
c.返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。
而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
e. 分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
f.自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。
delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
g.重载
C++允许重载new/delete操作符,特别的,重载new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
h.内存区域
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。
而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
export/extern关键字
对普通类型可以使用extern来声明(不定义它)这些变量或对象,使其他代码文件可以访问该变量或对象,而对于模板类型,在定义时使用export,在其他文件中便可以调用了
explicit
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
C++的异常处理机制
在C++中,异常的抛出和处理主要使用了以下三个关键字:try、 throw 、 catch。
#include<iostream>
#include<exception>
using namespace std;
int Div(int left,int right){
if(right==0){
throw exception("除数不能为0");
}
return left/right;
}
int main(){
try{
Div(10,20); //合法
Div(10,30); //合法
Div(10,0); //非法,会抛出异常
}catch(exception & e){
e.what(); //打印异常信息
}
return 0;
}
回调函数
当某个条件发生时,一个通过函数指针被调用的函数。
内存分配
在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区;
struct的字节数
typedef struct node2
{
char a;
int b;
short c;
}S2;
sizeof(S2)=12;
对于变量a,它的自身对齐参数为1,#pragma pack(n)默认值为8,则最终a的对齐参数为1,为其分配1字节的空间,
它相对于结构体起始地址的偏移量为0,能被4整除;
对于变量b,它的自身对齐参数为4,#pragma pack(n)默认值为8,则最终b的对齐参数为4,
接下来的地址相对于结构体的起始地址的偏移量为1,1不能够整除4,所以需要在a后面填充3字节使得偏移量达到4,然后再为b分配4字节的空间;
对于变量c,它的自身对齐参数为2,#pragma pack(n)默认值为8,则最终c的对齐参数为2,
而接下来的地址相对于结构体的起始地址的偏移量为8,能整除2,所以直接为c分配2字节的空间。
此时结构体所占的字节数为1+3+4+2=10字节
最后由于a,b,c的最终对齐参数分别为1,4,2,最大为4,#pragmapack(n)的默认值为8,
则结构体变量最后的大小必须能被4整除。而10不能够整除4,所以需要在后面填充2字节达到12字节。
注意事项:静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。
类占用字节数总结:
空类和空结构体类似,都会占用一个字节;
类中的数据成员也存在补齐,与结构体中情况类似,但补齐的标准长度不仅看子类,也看父类的最大对齐长度;
静态的数据成员不占用字节;
构造函数、析构函数、成员函数都不占用字节;
对于虚函数、纯虚函数需要额外注意:
虚函数和纯虚函数都不占用字节,但会申请指针,此时占用的空间为一个指针长度;
虚函数和纯虚函数 “字节占用数” 一样;
无继承时,多个虚函数、纯虚函数共享一个指针,占用字节为一个指针长度;
有继承时,有几个父类存在虚函数就需要多少个指针,此时子类的虚函数、纯虚函数不再占用字节;
C++中引用与指针的区别
(1)当引用被创建时,它必须被初始化。而指针则可以在任何时候被初始化。
(2)一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。而指针则可以在任何时候指向另一个对象。
(3)不可能有NULL引用。必须确保引用是和一块合法的存储单元关联。
野指针
创建后未初始化
free或delete后未置空
指针操作超越了变量作用范围
带参数的宏与函数的区别
1.函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
2.函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
3.对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
4.调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
5.使用宏次数多时,宏展开后源程序长,因为每展开一次都使程序增长,而函数调用不使源程序变长。
6.宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
取反
~a
define/typedef
define进行简单的宏替换,没有进行数据类型的检查,预处理指令,
typedef对数据类型取了别名,关键字
宏定义/内联函数
1.宏定义在预处理阶段进行代码替换,内联(inline)在编译阶段插入代码,
2.宏定义没有类型检查,内联函数有(不能包括复杂结构控制语句switch、while)
内联函数/普通函数
普通函数被调用时,系统访问函数的入口地址,执行函数体,执行完后,返回到函数调用的地方,函数只有一份复制
内联函数不需要寻址,执行内联函数时,进行展开,调用一次就会复制一次代码段
struct/union的区别
编译器为struct每个成员开辟内存空间,每个成员都可以赋值,只为union开辟其成员占字节数最大的大小的一个空间,且union只能为一个成员存值
C/C++中struct的区别
1.c中没有函数成员,C++有
2.c中数据成员没有private、public、protected的访问权限,C++有
3.c的struct没有继承关系,C++有
struct/class的区别
最本质的一个区别就是默认的访问控制: 默认的继承访问权限,struct是public的,class是private的。
大端模式/小端模式
大端模式从高地址向低地址存储
小端模式从低地址向高地址存储
重载/覆盖/隐藏
重载是指在同一个类中,同名但不同参数列表的函数之间的关系,与返回值无关
覆盖是子类与父类之间的关系,两个函数同名且参数列表和返回值均相同,但分别位于子类和父类中,且基类函数中必须有virtual关键字,
当派生类对象调用该函数时,子类的该函数便会覆盖掉父类的同名函数。
隐藏是指派生类的函数屏蔽了与其同名的基类函数,发生条件,派生类函数与基类函数同名,参数列表不同时,子类函数必将隐藏父类函数,参数列表相同时,没有virtual关键字,则为隐藏关系,有virtual关键字则为覆盖关系。
静态变量/全局变量
均存储在静态存储区,且生命周期与程序生命期相同,静态变量分为静态全局变量、静态局部变量,会有作用域的限制,
strcpy()函数
char* strcpy(char* strDest, const char* strSrc)
{
assert((strDest != NULL)&&(strSrc!=NULL));
if(strDest == strSrc)
return strDest;
char* address = strDest;
while((*strDest++ = *strSrc++)!='\0')
;
return addresss;
}
内存拷贝函数
void* MyMemCpy(void* dest, const void* src, size_t count)
{
char* pdest = static_cast<char*>(dest);//强制转换
const char* psrc = static_cast<char*>(src);
if((pdest>psrc)&&(pdest<(psrc+count)))
{
for(size_t i = count-1; i!= -1;--i)//当目的头地址位于想要拷贝的区间时
pdest[i] = psrc[i];
}
else
{
for(size_t i = 0; i<count;++i)
pdest[i] = psrc[i];
}
return dest;
}
面向对象的三个基本特征是:封装、继承、多态
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:允许将子类类型的指针赋值给父类类型的指针。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
深拷贝/浅拷贝
如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短
如果B没变,那就是深拷贝,自食其力。
即:拷贝时是否重新开辟内存空间(深拷贝),还是仅仅使两个指针指向同一个内存空间(浅拷贝)。
友元函数
友元函数不属于类的成员函数,即存在private/public都一样。不能直接访问类的成员,但可以通过类的对象间接访问,且不能被继承。
友元类
友元类的所有成员函数都是另一个类的友元函数,都可以间接访问另一个类中的私有成员
赋值运算符/复制构造函数
赋值运算符对已经存在的对象赋值,该对象以前具有内存空间
复制构造函数对新的对象赋值,该对象以前没有内存空间。
基类的构造函数/析构函数不能被继承
建立对象时,会先调用父类的构造函数再调用子类的构造函数。
销毁对象时,会先调用子类的析构函数再调用父类的析构函数
构造函数/析构函数定义为私有,该类便不能被继承。
public/protected/private
public:可以被任意实体访问
protected:只允许子类及本类的成员函数访问
private:只允许本类的成员函数访问
public/protected/private继承
1.public继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:public, protected, private
2.protected继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:protected, protected, private
3.private继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:private, private, private
构造函数的初始化成员列表
虚函数通过一张虚函数表来实现,一个虚函数的地址表
C++通过虚函数实现多态
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
随机数
从1到7的随机数 rand()%7+1;