标签:
引入虚函数是为了动态绑定,引入纯虚函数是为了派生接口
基类需要虚的析构函数的原因:
当derived class由一个base class指针被删除而该base class指针为non-virtual的时候,可能会发生内存泄漏,使用虚的析构函数可以解决该问题
//i++ 实现代码为:
int operator++(int)
{
int temp = *this;
++*this;
return temp;
}// 返回一个 int 型的对象本身
// ++i 实现代码为:
int& operator++()
{
*this += 1;
return *this;
}// 返回一个 int 型的对象引用
++i实际上返回的是一个可进行修改的左值,i++则不是
下面贴一个典型的题目:
int main()
{
int i = 1;
printf("%d,%d\n", ++i, ++i); //3,3
printf("%d,%d\n", ++i, i++); //5,3
printf("%d,%d\n", i++, i++); //6,5
printf("%d,%d\n", i++, ++i); //8,9
system("pause");
return 0;
}
上面代码的执行过程如下所示:
首先是函数的入栈顺序从右向左入栈的,计算顺序也是从右往左计算的,不过都是计算完以后在进行的压栈操作:
对于第5行代码,首先执行++i,返回值是i,这时i的值是2,再次执行++i,返回值是i,得到i=3,将i压入栈中,此时i为3,也就是压入3,3;
对于第6行代码,首先执行i++,返回值是原来的i,也就是3,再执行++i,返回值是i,依次将3,5压入栈中得到输出结果
对于第7行代码,首先执行i++,返回值是5,再执行i++返回值是6,依次将5,6压入栈中得到输出结果
对于第8行代码,首先执行++i,返回i,此时i为8,再执行i++,返回值是8,此时i为9,依次将i,8也就是9,8压入栈中,得到输出结果。
上面的分析也是基于vs搞的,不过准确来说函数多个参数的计算顺序是未定义的(the order of evaluation of function arguments are undefined)。笔试题目的运行结果随不同的编译器而异。
template里面出现的某个类型如果依赖于某个template参数,那么称之为从属类型,如果从属类型在class中呈嵌套状,那么称之为嵌套从属类型。
template<typename C>
void doSomething(const C & container)
{
if(container.size() > 0)
C::iterator iter(container.begin());
}
上面那种情况声明一个iter对象,那么前面加上typename才是合适的,上就是声明嵌套从属类型的方法
不可以,因为复制一个auto_ptr的时候,自身的值将会被置为NULL,复制一个auto_ptr相当于改变了这个智能指针的值
static成员一般在类外部初始化,但是const static的整型可以再类声明中进行初始化。static const的其他类型应该载类外进行初始化
总的思想是使用RAII:应该设计一个class,使其构造函数以及析构函数分别获得以及释放资源
因为函数的局部对象无论以什么样的方式结束都会调用析构函数,可以将一定要释放的资源放到析构函数中,就可以避免抛出异常的时候资源得以释放
简单的方法就是使用智能指针
RAII:resource allocation is initiation
继承描述符 | 父public成员 | 父protected成员 | 父private成员 |
---|---|---|---|
public | 子public成员 | 子protected成员 | 通过基类接口访问 |
protected | 子protected成员 | 子protected成员 | 通过基类接口访问 |
private | 子private成员 | 子private成员 | 通过基类接口访问 |
- 1、public:只继承基类的接口。当继承是接口的一部分时,就选用public继承。
- 2、private:只继承基类的实现。当继承是实现细节时,就选用private继承。
- 3、protected:当继承是面向派生类而不是面向用户接口中的一部分时,就选用protected继承。
单例模式的一般形式如下所示,将构造函数、析构函数、复制构造函数、赋值操作符声明为私有,即可实现单例模式
class Singleton{
private:
static Singleton * _instance;
protected:
Singleton();
public:
static Singleton * Instance();
};
Singleton::Singleton(){}
Singleton* Singleton::_instance = nullptr;
Singleton * Singleton::Instance()
{
if(_instance == nullptr)
_instance = new Singleton;
return _instance;
}
避免用户复制的行为:可以将拷贝构造函数设置为private或者是C++11中的delete语法
实现线程安全的单例模式:上面实现中的GetInstance()不是线程安全的,因为在单例的静态初始化中存在竞争条件。如果碰巧有多个线程在同时调用该方法,那么就有可能被构造多次。
比较简单的做法是在存在竞争条件的地方加上互斥锁。这样做的代价是开销比较高。因为每次方法调用时都需要加锁。
比较常用的做法是使用双重检查锁定模式(DCLP)。但是DCLP并不能保证在所有编译器和处理器内存模型下都能正常工作。如,共享内存的对称多处理器通常突发式提交内存写操作,这会造成不同线程的写操作重新排序。这种情况通常可以采用volatile解决,他能将读写操作同步到易变数据中,但这样在多线程环境下仍旧存在问题。
相等(equality)是以operator==为基础,如果x==y为真,则判定x和y相等。等价(equavalence)是以operator<为基础,如果!(x < y) && !(y < x)为真,则判定x和y等价。通常,关联容器采用“等价”,而顺序容器采用“相等”。
function object就是重载了函数调用操作符 operator()的一个struct或者class,所有内置一元仿函数均继承自unary_function,所有内置二元仿函数均继承自binary_function。继承自unary_function和binary_function的仿函数可以成为“可配接“的仿函数。可配接的仿函数,能够与其他STL组件更”和谐“地协同工作。
方法是使用mutable去除掉const成员函数的const属性
其中const_cast与mutable之间是有区别的:
const_cast:
强制去掉对象的const属性
缺点:对const对象,调用包含const_cast的const成员函数,属于未定义行为
1) 使用场景:对可能要发生变化的成员前,加上存储描述符mutable。
2) 实质:对加了mutable的成员,无视所有const声明。
为什么要有这种去除常量标志的需求?
答:两个概念:物理常量性和逻辑常量性
物理常量性:实际上就是常量。
逻辑常量性:对用户而言是常量,但在用户不能访问的细节上不是常量。
这个问题主要是针对连续内存容器和非连续内存容器。
a、对于连续内存容器,如vector、deque等,增减元素均会使得当前之后的所有迭代器失效。因此,以删除元素为例:由于erase()总是指向被删除元素的下一个元素的有效迭代器,因此,可以利用该连续内存容器的成员erase()函数的返回值。常见的编程写法为:
for(auto iter = myvec.begin(); iter != myvec.end()) //另外注意这里用 "!=" 而非 "<"
{
if(delete iter)
iter = myvec.erase(iter);
else ++iter;
}
vector插入元素时位置过于靠前,导致需要后移的元素太多,因此vector增加元素建议使用push_back而非insert;
(2)、当增加元素后整个vector的大小超过了预设,这时会导致vector重新分分配内存,效率极低。因此习惯的编程方法为:在声明了一个vector后,
立即调用reserve函数,令vector可以动态扩容。通常vector是按照之前大小的2倍来增长的。
b、对于非连续内存容器,如set、map等。增减元素只会使得当前迭代器无效。仍以删除元素为例,由于删除元素后,erase()返回的迭代器
将是无效的迭代器。因此,需要在调用erase()之前,就使得迭代器指向删除元素的下一个元素。常见的编程写法为:
for(auto iter = myset.begin(); iter != myset.end()) //另外注意这里用 "!=" 而非 "<"
{
if(delete iter)
myset.erase(iter++); //**使用一个后置自增就OK了**
else ++iter;
}
其实在C++11中erase的返回值就是下一个节点,也可以利用函数的返回值
下面首先对new进行一些说明:
new可分为operator new(new 操作)、new operator(new 操作符)和placement new(定位 new)。其中operator new执行和malloc相同的任务,即分配内存,但对构造函数一无所知;而new operator则调用operator new,分配内存后再调用对象构造函数进行对象的构造。其中operator new是可以重载的。placement new,就是operator new的一个重载版本,允许你在一个已经分配好的内存中构造一个新的对象。而网上对new说法,大多针对operator new而言,因此说new是带有类型的(以为调用了类的构造函数),不过如果直接就说new是带有类型的话,明显是不合适的,比如原生的operator new。下面这个程序是用代理模式实现一个自定义二维数组,在第二个维度拷贝构造的时候, 拷贝构造需要深拷贝(当然第一个维度也需要),执行深拷贝时代码大致如下:
class Array2D //二维数组模板
{
private:
size_t length2,length1; //数组各个维的大小
Array1D* data;
}
void* raw =::operator new%29”>;
data = static_cast
这是很复杂的一种情况,是关于类的copy constructor的。首先先介绍一些概念。
同default constructor一样,标准保证,如果类作者没有为class声明一个copy constructor,那么编译器会在需要的时候产生出来(这也是一个常考点:问道”如果类作者未定义出default/copy constructor,编译器会自动产生一个吗?”答案是否定的)
不过请注意!!这里编译器即使产生出来,也是为满足它的需求,而非类作者的需求!
而什么时候是编译器”需要”的时候呢?是在当这个class 【不表现出】bitwise copy semantics(位逐次拷贝,即浅拷贝)的时候。
在4中情况下class【不表现出】bitwise copy semantics
(1)、当class内含一个member object且该member object声明了一个copy constructor(无论该copy ctor是类作者自己生明的还是编译器合成的);
(2)、当class继承自一个base class且该base class有一个copy constructor(无论该copy ctor是类作者自己生明的还是编译器合成的)
(3)、当class声明了virtual function;
(4)、当class派生自一个继承链,且该链中存在virtual base class时。
言归正传,如果class中仅仅是一些普通资源,那么default memberwise copy是完全够用的;然而,在该class中存在了一块动态分配的内存,并且在之后执行了bitwise copy semantics后,将会有一个按位拷贝的对象和原来class中的某个成员指向同一块heap空间,当执行它们的析构函数后,该内存将被释放两次,这是未定义的行为。因此,在必要的时候需要使用user-defined explicit copy constructor,来避免内存泄露
STL中的sort(),在数据量大时,采用quicksort,分段递归排序;一旦分段后的数量小于某个门限值,改用Insertion sort,避免quicksort深度递归带来的过大的额外负担,如果递归层次过深,还会改用heapsort。
本质:指针是一个变量,存储内容是一个地址,指向内存的一个存储单元。而引用是原变量的一个别名,实质上和原变量是一个东西,是某块内存的别名。
指针的值可以为空,且非const指针可以被重新赋值以指向另一个不同的对象。而引用的值不能为空,并且引用在定义的时候必须初始化,一旦初始化,就和原变量“绑定”,不能更改这个绑定关系。
不过如下的写法也是同的过编译器的:
int *iptr = NULL;
int& iref = *iptr;
对指针执行sizeof()操作得到的是指针本身的大小(32位系统为4,64位系统为8)。而对引用执行sizeof()操作,由于引用本身只是一个被引用的别名,所以得到的是所绑定的对象的所占内存大小。
指针的自增(++)运算表示对地址的自增,自增大小要看所指向单元的类型。而引用的自增(++)运算表示对值的自增。
在作为函数参数进行传递时的区别:指针作为函数传输作为传递时,函数内部的指针形参是指针实参的一个副本,改变指针形参并不能改变指针实参的值,通过解引用*运算符来更改指针所指向的内存单元里的数据。而引用在作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。
总结下来需要注意的大概有下面几点:
1)、尽量避免使用raw pointer构建shared_ptr,至于原因此处不便于多讲,后续还有讲解
2)、shared_ptr使得依据共享生命周期而经行地资源管理进行垃圾回收更为方便
3)、shared_ptr对象的大小通常是unique_ptr的两倍,这个差异是由于Control Block导致的,并且shared_ptr的引用计数的操作是原子的
4)、默认的资源销毁是采用delete,但是shared_ptr也支持用户提供deleter,与unique_ptr不同,不同类型的deleter对shared_ptr的类型没有影响。
1、C++是面向对象语言,C是面向过程语言。
2、结构:C以结构体struct为核心结构;C++以类class为核心结构。
3、多态:C可以以宏定义的方式“自定义”部分地支持多态;C++自身提供多态,并以模板templates支持编译期多态,以虚函数virtual function支持运行期多态。
4、头文件的调用:C++用< >代替” “代表系统头文件;且复用C的头文件时,去掉”.h”在开头加上”C”。
5、输入输出:鉴于C++中以对象作为核心,输入和输出都是在流对象上的操作。
6、封装:C中的封装由于struct的特性全部为公有封装,C++中的封装由于class的特性更加完善、安全。
7、常见风格:C中常用宏定义来进行文本替换,不具有类型安全性;C++中常建议采用常量定义,具有类型安全性。
8、效率:常见的说法是同等目的C通常比C++更富有效率(这其实有一定的误解,主要在于C++代码更难于优化且少有人使用编译期求值的特性)。
9、常用语言/库特性:
a、数组:C中采用内建数组,C++中建议采用vector。相比之下vector的大小可以动态增长,且使用一些技巧后增长并不低效,且成员函数丰富。
b、字符串 C中采用C风格的string(实则为字符串数组),C++中建议采用string,对比与上一条类似。
c、内存分配:C中使用malloc与free,它们是是C标准库函数,C++中建议使用new/delete代替前者,他们说是C++的运算符(这是笔试面试常考点)以C++中的new为例,new可分为operator new(new 操作)、new operator(new 操作符)和placement new(定位 new)。其中operator new执行和malloc相同的任务,即分配内存,但对构造函数一无所知;而 new operator则调用operator new,分配内存后再调用对象构造函数进行对象的构造。其中operator new是可以重载的。placement new,就是operator new的一个重载版本,允许你在一个已经分配好的内存中构造一个新的对象。
d、指针:C中通常使用的是原生指针(raw pointer),由于常出现程序员在申请后忘记释放造成资源泄漏的问题,在C++98中加入了“第一代”基于引用计数的智能指针auto_ptr,由于初代的各种问题(主要是无法解决循环指针),在03标准也就是TR1中引入了shared_ptr,weak_ptr和unique_ptr这三个功能各异的智能指针,并与11标准中正式确定,较好的解决了上述问题。
仅有C++才有的常用特性:
1、语言(范式)特性:
a、面向对象编程:C++中以关键字class和多态特性支持的一种编程范式;
b、泛型编程:C++中以关键字template支持的一种编程范式;
c、模板元编程 :C++中以模板特化和模板递归调用机制支持的一种编程范式。
d、C++中以对象和类型作为整个程序的核心,在对象方面,时刻注意对象创建和析构的成本,例如有一个很常用的(具名)返回值优化((N)RVO);
在类型方面,有运行时类型信息(RTTI)等技术作为C++类型技术的支撑。
e、函数重载:C++允许拥有不同变量但具有相同函数名的函数(函数重载的编译器实现方式、函数重载和(主)模板特化的区别都曾考过)。
f、异常:以catch、throw、try等关键字支持的一种机制。
g、名字空间:namespace,可以避免和减少命名冲突且让代码具有更强的可读性。
h、谓词用法:通常以bool函数或仿函数(functor)或lambda函数的形式,出现在STL的大多数算法的第三个元素。
2、常见关键字(操作符)特性:
a、auto:在C中,auto代表自动类型通常都可省略;而在C++11新标准中,则起到一种“动态类型”的作用——通常在自动类型推导和decltype搭配使用。
b、空指针:在C中常以NULL代表空指针,在C++中根据新标准用nullptr来代表空指针。
c、&: 在C中仅代表取某个左值(lvalue)的地址,在C++中还可以表示引用(别名)。
d、&&:在C中仅能表示逻辑与,在C++中还可以表示右值引用。
e、[]:在C中仅能表示下标操作符,在C++中还可以表示lambda函数的捕捉列表。
f、{}:在C中仅能用于数组的初始化,在C++中由于引入了初始化列表(initializer_list),可用于任何类型、容器等的初始化。
g、常量定义:C中常以define来定义常量,C++中用const来定义运行期常量,用constexpr来定义编译器常量。
3、常用新特性:
a、右值引用和move语义(太多内容,建议自查)。
b、基于范围的for循环(与python中的写法类似,常用于容器)。
c、基于auto——decltype的自动类型推导。
d、lambda函数(一种局部、匿名函数,高效方便地出现在需要局部、匿名语义的地方)。
e、标准规范后的多线程库。
dynamic_cast失败的时候,对指针会返回NULL,对引用,会跑出bad_cast异常
还有就是c++中几种新的cast类型:
然后看一下各自的适用范围:
static_cast:static_cast基本上拥有与C旧式转型相同的威力和意义,以及相同的限制。但是,该类型转换操作符不能移除常量性,因为有一个专门的操作符用来移除常量性。
const_cast:用来改变表达式中的常量性(constness)或者易变形(volatileness),只能用于此功能。
dynamic_cast:将指向基类basic class object的pointer或者reference转型为指向派生类derived(或这sibling base)class object的pointer或者reference中,并且可以获知是否转型成功:如果转型失败,当转型对象是指针的时候会返回一个null指针;当转型对象是reference会抛出一个异常exception。dynamic_cast无法应用在缺乏虚函数的类型上,也不能改变类型的常量性。
此外,dynamic_cast还有一个用途就是找出被对象占用的内存的起始点。
reinterpret_cast:这个操作符的转换结果几乎总是和编译器平台相关,所以不具有移植性。reinterpret_cast的最常用用途是转换“函数指针”类型:
typedef void(*FuncPtr)();
int doSomething();
int main()
{
FuncPtr funcPtrArray[10];
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);
return 0;
}
语法上可以,但是语意上不可以
derived class对象内的base class成分会在derived class自身构造之前构造完毕。因此,在base class的构造函数中执行的virtual函数将会是base class的版本,决不会是derived class的版本.即使目前确实正在构造derived class。
浅拷贝:如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会根据需要生成一个默认的拷贝构造函数,完成对象之间的位拷贝。default memberwise copy即称为浅拷贝。
此处需要注意,并非像大多数人认为的“如果class未定义出copy constructor,那么编译器就会为之合成一个执行default memberwise copy语义的copy constructor”。
通常情况下,只有在default copy constructor被视为trivial时,才会发生上述情况。一个class,如果既没有任何base/member class含有copy constructor,也没有任何virtual base class或 virtual functions,它就会被视为trivial。通常情况下,浅拷贝是够用的。
深拷贝:然而在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。如果此时B中执行析构函数释放掉指向那一块堆的指针,这时A内的指针就将成为悬挂指针。 因此,这种情况下不能简单地复制指针,而应该复制“资源”,也就是再重新开辟一块同样大小的内存空间。
1.
template<typename T> void f(T);/* a */
template<typename T> void f(T*);/* b */
template< > void f<int>(int*);/* c */
int* p;
f(p);
2.
template<typename T> void f(T);/* a */
template< > void f<int*>(int*);/* b */
template<typename T> void f(T*);/* c */
int* p;
f(p);
分析前先回顾一下模板特化的东西。
(1)、非特化的模板也被称为主模板;
(2)、类模板能全特化和偏特化;
(3)、函数模板只能全特化,不过由于函数重载的原因,能达到偏特化的效果。
对于1来说:
a是第一个主模板,b是第二个主模板,且b是第一个主模板a的重载而非偏特化(函数模板没有偏特化)。c是第二个主模板b的显式特化(全特化)。
在f(p)调用时,发生重载决议,会无视特化存在(标准规定:重载决议无视模板特化,重载决议只会发生在主模板之间)。在主模板a和b中决议出b,即第二个主模板被决议选中,然后再调用其全特化版本c。
而对与2来说:
这里a是第一个主模板,b是第一个主模板a的全特化,c是第二个主模板。在f(p)调用时,发生重载决议,同样会无视特化存在,在主模板a和c中决议出c,而c并无全特化版本,因此直接调用c。
通常情况下是不能的
原因:inline是编译期决定,他意味着在执行前就将调用动作替换为被调用函数的本体;
virtual是运行期决定,他意味着直道运行期才决定调用哪个函数。
这两者之间通常是冲突的。
然而也有特例,就是当编译阶段就已经知道调用虚函数的指针为多态指针。这里就不再敖述了。
标准规定,凡是具有non-trivial constructor、non-trivial destructor、non-trivial copy constructor、non-trivial assignment operator的class对象都不能作为union的成员。
即是说,这个class的以上四种成员必须均经由编译器合成且该class无虚函数和虚基类。 有这种限制是为了兼容C。
如何实现一个不能在堆上分配的类,如果要在堆上分配就是会使用new,所以可以重载new 操作符,并将其重载于class A的private内:
class A
{
public:
A(int a):_x(a){}
int Display() {
return _x;
}
void setVal(int x) {
_x = x;
return;
}
private:
//
int _x;
void* operator new(size_t t){
}
};
//如何实现一个不能被继承的类,这里有一个比较简单的方法,利用C++11的新关键字final:
class B final {
public:
B(int a) {
}
};
这里要提及的首先有三点:
shared_ptr是原始指针大小的两倍。
引用计数的内存必须被动态分配
引用计数的改变(increments and decrements)必须是原子的
#include <memory>
#include <iostream>
using namespace std;
int main()
{
int *rpw = new int(12);
{
shared_ptr<int> isptr1(rpw);
cout << sizeof(rpw) << endl;
cout << sizeof(isptr1) << endl;
}
system("pause");
return 0;
}
明显看到验证了第一条。为什么会是两倍具体原因后面会分析与unique_ptr类似,shared_ptr使用delete作为默认的资源析构函数,但是也可以使用用户自己提供的删除函数(deleter)。不过与unique_ptr不同的是,(unique_ptr的deleter是unique_ptr的类型的一部分)shared_ptr的deleter的类型不再是shared_ptr的类型的一部分。如下示例:
auto del1 = [](int *p) {
cout << "del1" << endl;
delete p;
};
auto del2 = [](int *p) {
cout << "del2" << endl;
delete p;
};
shared_ptr<int> isptr1(new int(12), del1);
shared_ptr<int> isptr2(new int(10), del2);
vector<shared_ptr<int>> vsptr{ isptr1,isptr2 };
//这里的isptr1和isptr2的类型是一样的,而对于unique_ptr则不同:
auto del1 = [](int *p) {
cout << "del1" << endl;
delete p;
};
auto del2 = [](int *p) {
cout << "del2" << endl;
delete p;
};
unique_ptr<int, decltype(del1)> iuptr1(new int(12), del1);
unique_ptr<int, decltype(del2)> iuptr2(new int(10), del2);
这里的iuptr1和iuptr2是两种不同的类型。
回到上面的分析中,为什么shared_ptr的大小是raw pointer的两倍的呢?原因主要是在shared_ptr的内部不只有一个类似原始指针的指向object的指针,还有一个指向Control Block的指针:如上所示,清晰的看到shared_ptr的sizeof返回值应该是2个指针的大小,其中一个指针指向需要指向的object,另外一个指针指向Control Block。Control Block中包含了对这个shared_ptr控制所必需的一些信息,包括引用计数Reference Count、Weak Count、以及在Other Data中会存放用户指定的deleter函数,分配器(allocator)等。所以从shared_ptr的开销角度来说,接下主要是讨论Control Block的创建已经创建带来的问题和Control Block的开销。
一般来说在三种情况下会创建Control Block
通过raw pointer创建shared_ptr的时候
通过make_shared创建shared_ptr的时候
通过unique_ptr转化创建shared_ptr的时候
上面三种情况都会创建Control Block,但是问题就出在这个Control Block上,稍后讨论该部分的开销。如果使用同一个raw pointer创建shared_ptr就会出现两个不同的shared_ptr指向同一个raw pointer指向的资源,但是有两个不同的Control Block,当一个的引用计数为0的时候,就会调用deleter释放该资源,那么当另一个shared_ptr也要释放该资源的时候就会发生释放已经被释放的资源的错误,如下所示:
int *rpw = new int(12);
{
shared_ptr<int> isptr1(rpw);
shared_ptr<int> isptr2(rpw);
}
显然这种错误是致命的,因为有可能是发生在析构函数中,接下来还会导致资源泄漏,原本我们是为了防止资源泄漏的。所以,这里建议尽我们在使用shared_ptr的时候应当避免使用raw pointer创建shared_ptr,也就是尽量避免。
using namespace std;
int main()
{
unsigned char a = 0x45;
unsigned char b = ~a >> 4 + 1;
printf("b = %d\n", b);
system("pause");
return 0;
}
答案是250。这里按照常理来说答案实际上应该是2,也就是运算符顺序是:(~a)>>(4 + 1);但是还有一点应该注意的是:调试的过程中进入汇编指令。 可以看到高级语句转换为汇编语言以后,是先执行取反再位移的。 我们看到**eax是16位的寄存器,于是在机器中
0xA5的寄存中表达是0000 0000 1010 0101,取反是1111 1111 0101 1010,那么右移5位是
0000 0111 1111 1010,**由于是unsigned char型的只能表示低8位的数值,即250。
下图表明了c++中的运算符优先级:
!(x^(x-1))
int f(int x, int y)
{
return (x&y)+((x^y)>>1);
}
求值f(729, 271)=500
简单的方法来看这个问题就是,x&y是取相同的位与,这个的结果是x和y相同位的一半,x^y是取x和y的不同位,右移相当于除以2,所以这个函数的功能是取两个数的平均值。(729+271)/2=500;
int Add(int a, int b)
{
if (b == 0)
return a;
int sum, carry;
sum = a ^ b;
carry = (a & b) << 1;
return Add(sum, carry);
}
int max = ((a+b)+abs(a,b))/2;
int c = a - b;
c = (unsigned)(c)>>(sizeof(int) * 8 - 1);
void swap(int & a, int & b)
{
a = a ^ b;
b = a ^ b;//注意1.
a = a ^ b;//注意2.
}
1处:b = a ^ b ^ b; 所以这里就是a
2处:a = a ^ b ^ a; 所以这里就是b
上面这样做的好处是,不会出现溢出的问题
C++语言支持函数重载,C语言不支持函数重载。 函数被C++编译后在库中的名字与C语言的不同。 假设某个函数的原型为void foo(int x, int y)。 该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern “C”解决名字匹配问题,这样就可以正确的链接在库文件中已经编译好了的c函数
主要需要考虑的问题是对于类似16位机的溢出问题,所以可能需要加上UL限定符,让编译器可以识别
#define SECONDS_PER_YEAR (60*60*24*365)UL
将类成员的修饰符加上mutable就可以了。
指的注意的一点是,float和long实际上都是占4个字节
要明确sizeof不是函数,也不是一元运算符,它是个类似宏定义的特殊关键字,sizeof()。 括号内的内容在编译过程中是不被编译的,而是被替代类型,如int a=8;sizeof(a)。 在编译过程中,不管a的值是什么,只是被替换成类型sizeof(int),结果为4。如果sizeof(a=6)呢?也是一样地转换成a的类型。 但是要注意,因为a=6是不被编译的,所以执行完sizeof(a=6)后,a的值还是8,是不变的
inline一般用于下面这样的情况: - (1)一个函数不断被重复调用。 - (2)函数只有简单的几行,且函数内不包含for、 while、 switch语句。内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。 而宏只是一个简单的替换。**内联函数要做参数类型检查,这是内联函数跟宏相比的优势。**inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。对于短小的代码来说inline增加空间消耗换来的是效率提高,这方面和宏是一模一样的,但是inline在和宏相比没有付出任何额外代价的情况下更安全。 至于是否需要inline函数,就需要根据实际情况来取舍了。
可能会导致内存泄漏的问题,看下面这张图就大致的了解了为什么会这样:
这种方式申请内存使用下面的方式可以成功:
char * strA()
{
char str[] = "hello world!";
}
const char * strA()
{
char * str = "hello world!";
}
这里要明确的一点是,char * 与char []之间的区别:
char str[] = “hello world” 实际上分配了一个局部数组(这样由于内存被释放了,会打印出来乱码)
char * str = “hello world”实际上分配了一个全局数组(因为这里的str不是分配在栈上的,打印出来的值是正常的,字符串常量保存在只读的数据段,而不是像全局变量那样保存在普通数据段(静态存储区))
上面的还有一种改法就是:
const char * strA()
{
static char str[] = "hello world";
return str;
}
class A{
public:
int _a;
A(){
_a = 1;
}
void print(){
cout << _a << endl;
}
};
class B : public A{
public:
int _a;
B(){
_a = 2;
}
};
int main()
{
B b;
b.print();
cout << b._a << endl;
system("pause");
return 0;
}
A的内存模型为:
A:错误的,调用到实参才会分配空间。
B:函数需要在它被调用之前被声明,这个跟main()函数无关。
C:错误的,在主函数main中可以不写return语句,因为编译器会隐式返回0;但是在一
般函数中没return语句是不行的。
D:正确的。
标签:
原文地址:http://blog.csdn.net/eversliver/article/details/51834399