函数压栈顺序
从右往左。
所以
int g = 1;
printf("%d, %d", g, ++g);
结果为2,2
int g = 1;
printf("%d, %d", g, g++);
结果为2,1,说明g++.exe是解析一个参数就加一次,不是解析完一行再加。
条件解析顺序
从左往右,如果当前已经足以判断(&&前面是False或||前面是True),则不会计算后续条件。
boolalpha
函数名称,功能是把bool值显示为true或false。
cout << boolalpha << ( str1==str2 ) << endl;
(int &)
float a = 1.0f
cout<<(int &)a
从a的起始地址开始,读取sizeof(int)长度,当整数输出
类型长度:
类型 | 长度(字节) |
---|---|
void | 1(sizeof是1,但是有warning) |
空类 | 1(和void一样) |
bool | 1 |
char | 1 |
short | 2 |
int | 4 |
long | 4 |
float | 4 |
long long | 8 |
double | 8 |
long double | 12(32位系统),16(64位系统) |
所有指针 | 4(32位系统),8(64位系统) |
整数的存储方式
是倒着存的,如0xFFEEDDF7在内存中为F7 DD EE FF
所以:
unsigned int a = 0xFFEEDDF7;
unsigned char *b = (unsigned char *)&a;
printf("%x",b[0]);
输出为F7
高精度赋值给低精度
产生截断,类似于用低精度的指针直接指向高精度的首位,再赋值给低精度变量unsigned char b = *((unsigned char *)&a)
由于整数是倒着存放的,所以产生的是低位截断。
unsigned int a = 0xFFEEDDF7;
unsigned char b = a;
long long c = 0xAABBCCDD11223344;
int d = c;
printf("%x,%x",b,d);
输出为F7,11223344
运算符优先级
逻辑非,按位取反>乘除加减>位移>比较大小>按位与异或或>逻辑运算>赋值运算
消去最低位的1
x=x&(x-1)
该表达式消去了x二进制数中最低端的1,可用于计算x二进制数中有几个1,或者判断x是否是2的N次方。
位运算的方法求平均数
int x = 729;
int y = 271;
cout<<((x&y) + ((x^y)>>1));
输出为500,x&y表示x和y中相同的位,也等于x,y中相同位的平均数(因为他们相同),x^y表示x、y中不同的位相加,>>1表示除以2取平均数。综上,这个表达式就是取两个数的平均数。
只用位运算实现加法
int Add(int a, int b){
if(0 == b)return a;//没有进位则返回
int sum,carry;
sum = a^b;//不考虑进位的加法
carry = (a&b)<<1;//算出要进位的地方,并左移到该加的位置
return Add(sum,carry);//递归相加
}
交换两个数
a^=b
b^=a
a^=b
extern “C”
C++实现重载函数是将这个函数进行改名,在后面加上参数,如foo(int,int)编译后变成_foo_int_int_,用C去调用这个函数会出问题,因此加上extern "C"之后,则编译后会得到一个符合C规范的名字。
用宏获取结构体中某个元素相对于结构体头的偏移地址
# define FIND(struc,e) (size_t)&(((struc*)0)->e)
先将0强制转化为struc*类型,这样,它的首地址就是0,因此偏移量就等于e的绝对地址,再将其转换为int型的即可。
用宏定义一年有多少秒
# define (60 * 60 * 24 * 365)UL
注意后面的UL,以及在宏中,任何变量都应该用括号括起来
用宏定义MIN函数
# define MIN(A,B) ((A)<=(B)?(A):(B))
注意括号
关于const
申明 | 含义 |
---|---|
const int * a; | 指向的东西不能变 |
int const * a; | 通上 |
int * const a; | 指针本身不能变 |
int f()const; | 该函数不能改变成员变量 |
const int * f(); | 该函数返回一个指针,不能通过指针来改变指向的变量 |
总结:
- 在左边的const表示指向的东西不能变,在右边的const表示指针不能再指向其他变量。
- const成员函数中,若需改变成员变量,则将需要改变的成员变量用mutable关键字修饰。
结构体对齐
结构体为了读取方便,会对数据元素进行对齐,对齐方式就是取这里面最大的数据类型,为每一个数据所占的长度,其他的小的数据不够的话则补齐。
如有一下代码:
struct ST
{
int i;
bool b;
double d;
};
struct ST2
{
int i;
double d;
bool b;
};
cout<<sizeof(ST);//=16
cout<<sizeof(ST2);//=24
cout<<endl;
cout<<FIND(ST,i)<<FIND(ST,b)<<FIND(ST,d)<<sizeof(ST);
输出
16 24
0 4 8 16
这里,double是最长的元素,所以对齐长度取8,其中bool型的被压缩在i没填完的地方里面了。可见,一个结构体中元素出现顺序不一样,结构体的大小不一样。
|i|i|i|i|b| | | |
|—|—|—|—|
|d|d|d|d|d|d|d|d|
用# pragma pack(n)
可以更改对齐方式,如果的话,那么尽量按1对齐,如果大于1,如double、int等,那么就按它本身大小对齐。
堆与栈
一个由c/C++编译的程序占用的内存分为以下几个部分 :
1、栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆区(heap) ― 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵;
3、全局区(静态区)(static)―,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放 ;
4、文字常量区 ―常量字符串就是放在这里的。 程序结束后由系统释放 ;
5、程序代码区―存放函数体的二进制代码。
详见:
http://zhidao.baidu.com/link?url=ejKPzQ9sYih1Q3sYirWst8L1JmYVGopR6WQrfu2_c5zpAbdgoOvpxfzdTOYusNKUG7k9UqVnCM9jy2BxTXskk_
内存分配
malloc:值分配内存,返回的只有void指针,失败返回0
calloc:分配内存,并全部置0;参数为两个,一个是大小一个是多少
new:自动执行构造函数,自动计算内存大小,是运算符而不是函数,失败时抛出异常。
关于sizeof的一些补充
char * p = "123456";
char a[] = "123456";
cout<<sizeof(p);
cout<<sizeof(*p);
cout<<sizeof(a);
cout<<sizeof(*a);
输出4 1 7 1
1)注意sizeof一个数组名,得到的是数组占用空间大小,这是数组和指针的区别之一!
2)sizeof 是运算符不是函数,因此可以sizeof一个类型,如果是变量的话可以不加括号!sizeof a``sizeof (int)
3)sizeof 的返回类型是size_t,是一个与机器有关的无符号整型
4)数组传给sizeof的时候不退化,传给strlen时,退化为指针`,实际上,数组在传给任意函数之后都退化成了指针,在子函数中,不知道数组大小,需要在传一个参数进去。
5)sizeof 类似于一个特殊的宏,在编译时会将所有的变量、表达式替换为其相应的类型,因此,在程序运行过程中,sizeof括号里的标的是得不到执行。sizeof(a=4);
a的值是不会改变的,同理sizeof(f())
的f()也得不到执行。
6)sizeof不能用于计算动态数组的大小
虚函数
作用:用基类指针去调用子类函数,实现多态。
实现方法:虚函数表,在一个有虚函数的类的开头有虚函数指针,里面指向了一个虚函数的列表如图:
对于多重继承就会有多个虚函数表的指针,就像这样:
因此对于一个没有虚函数的类,sizeof(B)=1,对于单继承的有虚函数的类,sizeof(D)=4,而对于多继承则sizeof(D)=4*n
一般来讲,虚函数指针放在对象的第一个位置,因此其的地址就是类的地址。我们可以通过函数指针强行指向类的第一个指针指向的函数地址,来实现调用,这种方法可以直接调用private函数,是C++的一个bug。
详见 http://blog.csdn.net/haoel/article/details/1948051/
内联函数
编译器会将代码直接替换到调用的地方,会做检查,而宏则不会。inline
应与函数定义放在一起,而不是声明。内联函数常用于一些大型工程。
指针
指针和数组名的区别
指针占空间,数组名不占空间
char * c="hello";//"hello"分配在常量区
char c[]="hello";//"hello"分配在栈上这个c不占空间,sizeof(c)
得到的是数组长度。我怀疑是编译之后直接用一个常地址给替换了
const char * strA()
{
char c[] = "hello";//"hello"分配在栈上,程序结束即消除,
//所以这种写法错误
return c;
}
const char * strA()
{
static char c[] = "hello";//"hello"分配在常量和全局区,
//返回之后不会消失,合法
return c;
}
指针减法
会自动除以一个sizeof(当前类型)
用派生类指针指向基类
一般我们用基类指针指向派生类,实现多态,
而用派生类指针指向基类是非法的,如果非要这么做,则需使用dynamic_cast
Derived *d = dynamic_cast<Derived *>( new Base());
main函数返回值
如果没写,则编译器自动返回0,所有函数都必须有返回值。
一些指针
指针类型 | 写法 | 备注 |
---|---|---|
函数指针 | void (*pf)() | 声明时一定要加类型void (*pf)(int,int)=&f; |
函数返回指针 | void *f() | |
const指针 | const int * p | 指向的东西不能变,指向const int a,因此直接加了个* |
指向const的指针 | int * const p | p本身的值不能变,而指向的东西可以变 |
指向const的const指针 | const int * const p | 上面两个合起来 |
一些指针示例
语句 | 含义 |
---|---|
float(**def)[10] |
def是一个二级指针,指向一个指向一维数组的指针,一位数组元素都是float |
double*(*gh)[10] |
gh是一个指针,指向一个一维数组,数组元素都是double * |
double(*f[10])() |
f是一个数组,有10个元素,元素都是函数指针,指向返回double型的无参数的函数 |
int*((*b)[10]) |
和int*(*b[10]) 相同,是指向一维数组的指针 |
long (*fun)(int) |
函数指针 |
int(*(*f)(int,int))(int) |
f是一个函数指针,指向一个以两个int为参数,返回一个函数指针的函数,返回的函数输入一个int输出一个int |
int * a[10] |
一个数组,有10个int* 元素 |
int (*a)[10] |
一个指针,指向一个有10个int元素的数组 |
以int * (*p)[10] 为例说明判断方式,先找到变量,这是一个指针,然后看最左边,表示的是数据类型是指向整型的指针,右边表示是指向数组的指针。综上,是一个指向一位数组的指针,一位数组里都是int * 。 |
数组名
数组名的值是个指针常量,《C和指针》p142中说到,在以下两中场合下,数组名并不是用指针常量来表示,就是当数组名作为sizeof操作符和单目操作符&的操作数时。 sizeof返回整个数组的长度。 &a产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针。所以&a后返回的指针便是指向数组的指针,跟a(一个指向a[0]的指针)在指针的类型上是有区别的。
定义char a[] = “hello”;
语句 | 含义 | sizeof | cout |
---|---|---|---|
a | 常指针,指向a[0] | 6 | hello |
a+1 | 常指针,指向a[1] | 4 | ello |
*a | 取a的第0个元素 | 1 | h |
&a | 获得一个指向数组的指针变量 | 4 | 0x22ff0a |
&a+1 | 指向首地址加上数组长度的那个元素,已经超出数组范围 | - | 22ff10 |
*&a | 获得上面指针所指示的数组 | 6 | hello |
代码:
char a[] = "hello";
cout << sizeof(a)<<" "<< sizeof(&a)<<" "<< sizeof(*a)<<" "<< sizeof(*&a)<<" ";
cout << (a)<<" "<< (&a)<<" "<< (*a)<<" "<< (*&a)<<" ";
输出
6 4 1 6 hello 0x22ff0a h hello
总的来说,a是一个指向数组第0个元素的指针,长度为1,而&为指向数组的指针,长度为n,指示在sizeof(a)的时候变为n,比较特殊。
指针和句柄
句柄是系统提供的系统资源虚拟地址,类似于指针,但是windows会经常将空闲对象释放,需要用时重新加载,因此用物理地址就没法找到,因此需要引入指针来管理。
智能指针
1、在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想,而且极容易出错。不能复制,因此其管理的对象不能放入 std::vector 等容器中。
2、在确定对象无需共享的情况下,使用 boost::scoped_ptr(当然动态数组使用boost::scoped_array)。
3、在对象需要共享的情况下,使用 boost::shared_ptr(当然动态数组使用boost::shared_array)。
4、在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下,使用boost::weak_ptr,一般常用于软件框架设计中。
5、最后一点,也是要求最苛刻一点:在你的代码中,不要出现 delete 关键字(或 C 语言的free 函数),因为可以用智能指针去管理。
来源: http://blog.csdn.net/xt_xiaotian/article/details/5714477
this指针
C++的this指针和Python一样,其实是成员函数的一个隐藏参数,而在成员函数中,亦是通过this指针来区别不同的对象。静态成员函数没有this指针。
STL
浅拷贝
含有指针的对象,如果只进行浅拷贝,即直接复制的话,会引发指针重复析构的问题。因此,含有指针的对象最好不要放到容器中,或者定义拷贝构造函数,使用深拷贝。
面向对象
面向对象五个基本原则
- 单一职责(Single-Responsibility Principle):就一个类而言,应该仅有一个引起它变化的原因。防止原因间相互交叉。
- 开放封闭原则(Open-Closed Principle):是说软件实体(类、模块、函数等等)应该可以扩展的,但是不可修改。
- 依赖倒置原则(Dependency-Inversion Principle):抽象不应该依赖细节,细节应该依赖于抽象。
- 里氏替换原则(Liskov-Substituent Principe.):子类必须能够替换掉它们的父类。其意思:子类必须具有父类的所有特性
- 接口隔离原则(Interface-Segregation Principle):多个专用接口优于一个单一的通用接口。其意思:不要将所有的方法都添加到一个接口中。
编译器默认生成的四个成员函数
构造函数、析构函数、拷贝构造函数、赋值构造函数。
其中
- 赋值构造函数就是重载等号;
- 拷贝构造函数用于生成新对象的时候,此时,即使是用
A a1=a
,调用的也是拷贝构造函数,不会调用重载等号。
生成类对象要不要加括号
如果构造函数有参数,显然要加括号,但是,如果构造函数没有参数,则不能加括号,加了就变成函数声明了。
例如:
A a(1);
B b;
B b();//错误,这是声明b是一个返回值为B类型的函数。
静态成员
-
静态成员变量:非const型static变量只能在类声明外初始化。
-
静态成员函数:可以直接使用A::f()的形式调用,不用生成对象。
class A
{
public:
const static int a = 1;//const static可以直接初始化
static int b;
static int f();
};
int A::b = 2;//初始化,不能加static
int main()
{
…
}
初始化列表
-
在构造函数的定义中指定;
-
带参数对象(成员对象、基类对象)、引用成员、const只能在初始化列表中初始化;
-
初始化顺序由成员定义的顺序决定,跟初始化列表里的顺序无关(考点)。
class A
{
public:
A(int i):b(i++),a(i++),c(0){}
int a;
int b;
const int c;
};
int main()
{
A t(1);
cout<<t.a<<" “<<t.b<<” “<<t.c<<” ";
}
打印 1 2 0
成员变量初始化位置
类型 | 初始化位置 |
---|---|
const | 构造函数初始化列表 |
static | 在类外定义 |
const static | 在声明时定义 |
class A
{
public:
static int a;
const int b;
const static int c = 0;
A():b(0){}
};
int A::a = 0;
构造函数和析构函数
构造函数 | 析构函数 | |
---|---|---|
private | 防止类被实例化,实现单例模式;或者只提供一些工具 | 只在堆上生成实例,如果在栈上生成实例,函数退出,会有自动析构,编译不通过。需自己添加析构函数Destory() |
virtual | 禁止这种写法。因为对象还没构造好,不能动态绑定 | 要实现多态,需采用虚析构函数,保证用delete基函数指针时,能调用派生类的析构函数 |
继承和多态
继承实现了代码重用
多态实现了接口重用
覆盖(override)和重载(overload)
覆盖通过虚函数的方式
重载通过不同参数的方式
异常
- 构造函数中抛出异常时,程序会按构造顺序,逆序析构已经构造的变量;
- 析构函数最好不要抛出异常,因为会导致资源无法释放;
- 最好不要直接使用指针,因为在构造函数构造抛出异常时,无法自动释放指针,最好用智能指针。
虚函数
虚函数使用的成员变量视传入他自己的this指针而定。也就是说,是哪一个类的虚函数,就调用哪一个类的变量。
继承规则
下表表示不同的基类成员类型在不同继承方式下,在子类里面的访问类型。注意基类的private和变成子类private的区别,基类是private,外界和子类都不能访问,变成子类private类型,子类可以访问,外界不能访问。
继承方式\成员类型 | public | protected | private |
---|---|---|---|
public | public | protected | 不能访问 |
protected | protected | protected | 不能访问 |
private | private | private | 不能访问 |
多重继承
多重继承能节省空间,避免冲突。
A A A
\ / / \
B C → B C
\ / \ /
D D
声明方式:
class A;
class B:public virtual A;
class C:public virtual A;
class D:public B,public C;
如果B,C中有同名变量,则需在D中用B::a,C::a区别。
虚继承中,每个父类都保留了自己的虚函数表,普通继承中总共只有一个虚函数表。
纯虚函数
纯虚函数定义为virtual int f()=0
,含有纯虚函数的类不能实例化对象。子类如果没有将基类的纯虚函数重写完,那么它仍然含有纯虚函数,仍然不能被实例化。