读书笔记之:程序员求职成功路(1)
第 1章 C语言
9. 智能指针
智能指针是C++标准库中一个重要的概念。
C++编程领域包括两种智能指针的实现:auto_ptr和shared_ptr
auto_ptr是C++标准库中的一个实现,位于<memory>文件中。
shared_ptr是Boost库中的一个实现,位于<boost/shared_ptr.hpp>文件中
智能指针并非是完美无缺的。auto_ptr存在如下的问题:
(1)auto_ptr不能共享所有权。
(2)auto_ptr不能指向数组。 因为其实现中调用的是delete而非delete[]
(3)auto_ptr不能作为容器的成员。
(4)auto_ptr只能通过直接初始化的方式来进行初始化。
auto_ptr<int> p(new int(42));//对
auto_ptr<int> p=new int(4);//错
auto_ptr的实现代码:
1 指针
C语言中定义字符串的两种形式:
char a[]="hello world";
char *b="hello world";
对于这两种形式的区别和联系可以使用下面的操作进行区别:
(1)sizeof(a)=strlen(a)+1;//主要是C语言中对于字符串的存储特点,会自动的以'\0'作为结束标志。
sizeof(b)=4;
(2)strlen(a)==strlen(b);//因为a和b其实都是定义了一个字符串。
(3)从实现的本质上看,a是一个数组,在进行程序编译之后是一个存在于符号表中,其对应的数组内存是在静态区分配的,而b是一个指针,应该是存储在静态区的,它指向的字符串应该作为一个不可改变的数据存储的。
非常重要的一点是:a作为一个数组,他的内容是改变的,而b的内容是不能改变的,强行改变会在运行时出错。
2. sizeof总结
siezof是一个操作符,它不是函数
(1)其参数可以作为数据类型或者为一般变量
例如:sizeof(int),sizeof(double)
(2) 参数可以是数组或指针
但是,需要注意的一点是当数组作为函数参数的时候会退化为指针,这时再在函数中使用sizeof的话就是测试指针的大小了
(3)参数可以是结构或类
需要注意两点:结构或类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或类的实例地址无关。第二,没有任何成员的结构或类的大小为1,因为必须保证结构或类的每一个实例在内存中都有唯一的地址。第三类中函数对类的大小没有影响。
3. 野指针
野指针不是NULL指针,而是指向“垃圾”内存的指针。野指针很危险,它可能造成不该访问的数据或不该修改的数据被访问或者篡改。在应用free或delete释放了指针指向的内存之后,应该将指针重新初始化为NULL,这样就可以防止野指针。
4. 位运算
(1). 异或运算
异或运算的逆运算就是其本身,所以两次异或同一个数的结果不变,即a^b^b=a
定义两个运算#和@,且互为逆运算,同时#满足交换律,那么下面实现x和y的交换:
x=x#y;
y=x@y;
x=x@y;
加法和减法是逆运算,并且加法满足交换律,所以一种可能是#为加法,@为减法。
异或运算是逆运算并且满足交换律,所以#和@同时为异或运算。
野指针不是NULL指针,而是指向“垃圾”内存的指针。野指针很危险,它可能造成不该访问的数据或不该修改的数据被访问或者篡改。在应用free或delete释放了指针指向的内存之后,应该将指针重新初始化为NULL,这样就可以防止野指针。
4. 位运算
(1). 异或运算
异或运算的逆运算就是其本身,所以两次异或同一个数的结果不变,即a^b^b=a
定义两个运算#和@,且互为逆运算,同时#满足交换律,那么下面实现x和y的交换:
x=x#y;
y=x@y;
x=x@y;
加法和减法是逆运算,并且加法满足交换律,所以一种可能是#为加法,@为减法。
异或运算是逆运算并且满足交换律,所以#和@同时为异或运算。
(2)常见二进制位变换
去掉最后一位 (101101->10110) x>>1
在最后加一个0 (101101->1011010) x<<1
在最后加一个1 (101101->1011011) (x<<1)+1
把最后一位变成1 (101100->101101) x|1
把最后一位变成0 (101101->101100) (x|1)-1
最后一位取反 (101101->101100) x^1
把右数第k位变成1 (101001->101101,k=3) x|(1<<(k-1))
把右数第k位变成0 (101101->101001,k=3) x&~(1<<(k-1))
右数第k位取反 (101001->101101,k=3) x^(1<<(k-1))
取末三位 (1101101->101) x&7
取末k位 (1101101->1101,k=5) x&((1<<k)-1)
取右数第k位 (1101101->1,k=4) x>>(k-1)&1
把末k位变成1 (101001->101111,k=4) x|((1<<k)-1)
末k位取反 (101001->100110,k=4) x^((1<<k)-1)
去掉整数最右边的1 (100101111->100101110) x&(x-1)
把右边连续的1变成0 (100101111->100100000) x&(x+1)
把右起第一个0变成1 (100101111->100111111) x|(x+1)
把右边连续的0变成1 (11011000->11011111) x|(x-1)
去掉右起第一个1的左边 (100101000->1000) x&(x^(x-1))
取右边连续的1 (100101111->1111) (x^(x+1))>>1
(3)将第n位置位或清零
在最后加一个0 (101101->1011010) x<<1
在最后加一个1 (101101->1011011) (x<<1)+1
把最后一位变成1 (101100->101101) x|1
把最后一位变成0 (101101->101100) (x|1)-1
最后一位取反 (101101->101100) x^1
把右数第k位变成1 (101001->101101,k=3) x|(1<<(k-1))
把右数第k位变成0 (101101->101001,k=3) x&~(1<<(k-1))
右数第k位取反 (101001->101101,k=3) x^(1<<(k-1))
取末三位 (1101101->101) x&7
取末k位 (1101101->1101,k=5) x&((1<<k)-1)
取右数第k位 (1101101->1,k=4) x>>(k-1)&1
把末k位变成1 (101001->101111,k=4) x|((1<<k)-1)
末k位取反 (101001->100110,k=4) x^((1<<k)-1)
去掉整数最右边的1 (100101111->100101110) x&(x-1)
把右边连续的1变成0 (100101111->100100000) x&(x+1)
把右起第一个0变成1 (100101111->100111111) x|(x+1)
把右边连续的0变成1 (11011000->11011111) x|(x-1)
去掉右起第一个1的左边 (100101000->1000) x&(x^(x-1))
取右边连续的1 (100101111->1111) (x^(x+1))>>1
置位:a|=(0x1<<n)
清零:a&=~(0x1<<n)
清除整数a最右边的1:a&(a-1)
将一个整数对齐到n:a=(a+n-1)&~(n-1)
常用位运算记录(参考:
http://blog.chinaunix.net/uid-20561882-id-2793594.html):
1) 判断int型变量a是奇数还是偶数
a & 1 = 0 偶数 (与1相加能进位说明个位是1,个位为0)
a & 1 = 1 奇数 (与1相加不能进位说明个位是0,个位为1)
或者
a&1
判断奇偶也就是判断个位上的数
a = 10010001
b=1
a&b = 0 != b 说明第1位因为是1,所以可进位,所以a的值改变
b=10 a&b = a 说明第二位是0,因为每进位
a&b = b 说明判断为是0,因为 0&b = b
(2) 取int型变量a的第k位 (k=0,1,2……sizeof(int)),即a> > k& 1
因为我要取第k位,那么把第k位移到第0位,然后擦掉其他位
(3) 将int型变量a的第k位清0,即a=a& ~(1< < k)
(4) 将int型变量a的第k位置1, 即a=a|(1< < k)
(5) int型变量循环左移k次,即a=a< < k|a> > 16-k (设sizeof(int)=16)
因为限定是16的循环,那么左移很可能移出去,但是就像地球是圆的一样,从左边跑可以跑到,从右边跑也可以跑到,所以向左移多少就是向右移多少的补 (k+16-k = 16 就是跑了一圈),所以思想是当向左跑跑出界了的话,那么就把向右跑给插入进去吧。。。。
(6) int型变量a循环右移k次,即a=a> > k|a< < 16-k (设sizeof(int)=16)
定理1:设a,b为两个二进制数,则a+b = a^b + (a&b)<<1。
证明:a^b是不考虑进位时加法结果。当二进制位同时为1时,才有进位,因此 (a&b)<<1是进位产生的值,称为进位补偿。将两者相加便是完整加法结果。
定理2:使用定理1可以实现只用位运算进行加法运算。
证明:利用定理1中的等式不停对自身进行迭代。每迭代一次,进位补偿右边就多一位0,因此最多需要加数二进制位长度次迭代,进位补偿就变为0,这时运算结束。
(7)整数的平均值
对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:
int average(int x, int y) //返回X,Y 的平均值
{
return (x& y)+((x^y)> > 1);
}
x+y = x^y+(x&y)<<1;
x*2 = x<<1;
x/2 = x>>1;
所以(x+y)/2 = (x^y + x&y<<1)>>1 = x^y>>1 + x&y
512 * 7 = 512 * (4 + 2 + 1) = 512 * 4 + 512 * 2 + 512
= 512 << 2 + 512 << 1 + 51
512/7 因为不能约分,又不是2的倍数,所以除不尽,所以只能近似
512/8 = 512>>3
5. 内存对齐
常见的内存对齐问题包括两个:自然对齐和强制对齐。
各个类型的自然对齐是其内存地址必须是其类型本身的整数倍。结构体对齐到其中成员最大长度类型的整数倍。
计算机中内存空间按照字节划分,从理论上讲似乎对任何类型的变量地址访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序地一个接一个第的排放,这就是对齐。
如果不按照适合其平台要求对数据存放进行对齐,一般会带来效率上的损失。比如,有些平台每次读都是从偶地址开始,如果一个int型存放在偶地址开始的地 方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读的结果的高低字节进行拼凑才能得到该数据。
6. 浮点数
单浮点数float和双浮点符double
浮点数存储中涉及的是符号位,阶码(指数)和尾数
对于float是4个字节中32位:8位阶码,23位尾数
对于double是8个字节中64位:16位解码,47位尾数
具体如下:
![](https://www.evernote.com/shard/s200/res/e38ca0bb-91e5-4b1d-bd7b-26f686f052fa/1340355349328.jpg)
其内存结构采用代码描述如下:
typedef strtuct _float{
bool sign:1;
char expon:8;
unsinged int mant:23;
}float
7.整数存储
整数编码常见的有原码,反码和补码
原码:在数值面前增加一个符号位
反码:正数的反码是其本身,负数的反码是对其绝对值逐位取反。
补码:正数的补码与原码相同,负数的补码是取模(计算方法是对其绝对值取反加1)
原码无法满足运算要求,所以对除符号位外的其余逐位取反就产生了反码。反码与原码的取值空间是一一对应的。但是在进行反码减法运算的时候出现了正零(0x00)和负零(0x80)
补码设计的目的:
(1)使符号位能与有效值部分一起参加运算,从而简化运算规则。
(2)使减法运算转化为加法运算,进一步简化计算机中运算器的电路设计。
补码的范围是[-128,127],-128没有对应的原码和反码。
8. 字符编码
常见的字符编码是ASCII码和UNICODE码
常见ASCII码的大小规则:0<..<9<A<..<Z<a<..z
A:65, 0x41
a:97,0x61
0:48, 0x30
' ':32,0x20
在C语言中,要区分多字节字符串与宽字符字符串的区别。在多字节字符串中,每个字符的编码宽度都不等,可以是一个字节,也可以是多个字节。
char* str="Hello world! 你好,世界!";
这就是一个多字节字符串,英文占一个字节,中文占两个字节。
wchar_t *wstr=L"Hello world!你好!世界!";
这个是宽字节字符串,其中的所有字符都是占2个字节
9. 溢出问题
溢出是C语言中最常见的漏洞。主要包括数组溢出,数溢出,缓冲区溢出,指针溢出和栈溢出。由于系统存在着各种溢出漏洞,就给病毒和恶意程序提供了传播和攻击的可能。比如冲击波病毒,就是利用了windows系统中的缓冲区溢出漏洞。
数组溢出就是常见的数组越界
缓冲区溢出是一个比较容易被攻击的问题。比如下面的一个小程序:
#include <stdio.h>
#include <stdlib.h>
void func(){
printf( " Hacked by me\n ");
exit( 1);
}
int main(){
int buf[ 1];
buf[ 2]=( int)func;
return 0;
}
#include <stdlib.h>
void func(){
printf( " Hacked by me\n ");
exit( 1);
}
int main(){
int buf[ 1];
buf[ 2]=( int)func;
return 0;
}
这个程序运行之后会输出:Hacked by me
这儿就是利用了缓冲区溢出的问题。buf[2]=(int)func;这一句发生了缓冲区溢出,利用func的地址覆盖了buf+2处的内存内容。如果对 函数运行栈比较熟悉的话,会发现被覆盖的正好就是main函数的返回地址。所以将main函数的返回地址变成了func的入口地址。
![](https://www.evernote.com/shard/s200/res/0dd1f1f2-93f3-4bc6-92d1-a84ff3a53862/1340460421414.jpg)
第2章 C++
1. C/C++的区别
C和C++的区别应该有很多方面
首先在思想上,C是面向过程的,而C++是一个面向对象的语言
其次就是在一些细节上。因为C++是在C的基础上发展起来的,并且C++在开始设计的时候其中的一个目标就是要与C的兼容性。
现在的C++98是包括C89的。C++在C的基础上增加了很多的特性,包括类,虚函数,函数重载(运算符重载),多重继承,模板,异常,名字空间等等。 并且增加了很多的关键字包括inline,const。所以如果要区别C++与C的话,最主要的就是分析C++中所兼容的那部分C与单独的C89的区别, 也就是那些在C中可以使用而在C++中却无法使用的规则。
C++中引入了函数重载,同时为了对应这个特性,在进行编译的时候,C++编译器和C编译器对函数名的处理采用了一种不同的方法,即函数名重整(name mangling),如果要在C++中引入C代码必须使用extern "C"来声明.
2. 类的构造函数(复制构造函数),析构函数和赋值操作符重载
这3个函数被成为C++的“三驾马车”。
对于一个空类,C++编译器会默认生成的函数包括:一个拷贝构造函数,一个赋值运算符重载,一个析构函数,一对取址运算符。如果没有声明构造函数,编译器会默认生成一个默认构造函数。
3. 模板与继承
模板和继承都是目前最常用的源代码重用方法。
4. 菱形继承
虚继承就是来解决菱形继承中的问题。
5. 虚函数的覆盖
(1)基类的静态方法不能被派生类覆盖为非静态方法。派生类可以定义为与基类的静态方法相同的静态方法,以便派生类隐藏基类的静态方法。
(2)基类的非静态方法不能被派生类覆盖为静态方法。
6. STL模板库
这儿是对STL一个比较好的介绍
http://www.cnblogs.com/xkfz007/archive/2012/06/29/2570338.html
这儿是对SGI对STL库的实现
其中包括了一些在STL标准库中不存在的容器
7. 写时拷贝
写时拷贝(Copy On Write)是为了让多个对象共享一个值。
为了让多个对象共享同一份实值,引入了引用计数,以统计这个实值被多少对象所共享。
8. 禁止或要求对象产生于堆中
(1)禁止对象产生于堆中
将new和delete声明为private权限,禁止被调用即可。
class Demo{public:private:static void *perator new(size_t size);static void operator delete(void*ptr);}Demo *pd=new Demo();//错误Demo demo;//正确
1.禁止对象产生于堆中
将new和delete声明为private权限,禁止被调用即可。
将new和delete声明为private权限,禁止被调用即可。
2.禁止对象产生非堆对象
将析构函数声明为protected或者private权限 。
将析构函数声明为protected或者private权限 。
9. 智能指针
智能指针是C++标准库中一个重要的概念。
C++编程领域包括两种智能指针的实现:auto_ptr和shared_ptr
auto_ptr是C++标准库中的一个实现,位于<memory>文件中。
shared_ptr是Boost库中的一个实现,位于<boost/shared_ptr.hpp>文件中
智能指针并非是完美无缺的。auto_ptr存在如下的问题:
(1)auto_ptr不能共享所有权。
(2)auto_ptr不能指向数组。 因为其实现中调用的是delete而非delete[]
(3)auto_ptr不能作为容器的成员。
(4)auto_ptr只能通过直接初始化的方式来进行初始化。
auto_ptr<int> p(new int(42));//对
auto_ptr<int> p=new int(4);//错
auto_ptr的实现代码:
template<typename _Tp>
class auto_ptr
{
private:
_Tp* _M_ptr;
public:
typedef _Tp element_type;
// 构造函数
// explicit表示该构造函数禁止隐式类型转换,即传入的参数必须是_Tp*类型
explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) {}
// 同类型拷贝构造函数,参数是另一个auto_ptr,但该auto_ptr要释放自己包含的指针
// 这是一个move(因此:千万不要使用类型为auto_ptr的容器)
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}
// 不同类型的拷贝构造函数
// 也是move
template<typename _Tpl>
auto_ptr(auto_ptr<_Tpl>& __a) throw() : _M_ptr(__a.release()) {}
// 同类型auto_ptr赋值操作符,__a释放自己包含的指针
// 本模板类用reset更新自己的指针为__a指针
auto_ptr& operator=(auto_ptr& __a) throw()
{
reset(__a.release());
return * this;
}
// 不同类型的auto_ptr赋值操作符
template<typename _Tpl>
auto_ptr& operator=(auto_ptr<_Tpl>& __a) throw()
{
reset(__a.release());
return * this;
}
// 析构函数
// 仅支持delete,不支持delete[]
~auto_ptr() { delete _M_ptr; }
// 重载dereference操作符
element_type& operator* const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}
// 重载->操作符
element_type* operator->() cosnt throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
// 获取成员指针函数
element_type* get() const throw() { return _M_ptr; }
// 获取成员指针函数,同时将成员指针清零
element_type* release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return _tmp;
}
// 释放成员指针所指对象,同时给成员指针赋新值
void reset(element_type* __p = 0) throw()
{
if(__p != _M_ptr)
{
delete _M_ptr;
_M_ptr = __p;
}
}
// 从auto_ptr_ref构造auto_ptr的构造函数
auto_ptr(auto_ptr_ref<element_type> __ref) throw() : _M_ptr(__ref._M_ptr) { }
// 从auto_ptr_ref的赋值操作符
// 因为这里要释放内存,所以要进行self-assignment检查
auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw()
{
if(__ref._M_ptr != this. get())
{
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return * this.
}
// 本auto_ptr到其他任意类型auto_ptr_ref的conversation操作符重载
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw()
{
return auto_ptr_ref<_Tp1>( this->release());
}
};
// 模拟auto_ptr的reference类型
// 可被一个传回auto_ptr值的函数赋值
template<typename _Tp1> struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit auto_ptr_ref(_Tpl* __p) : _M_ptr(__p) { }
};
{
private:
_Tp* _M_ptr;
public:
typedef _Tp element_type;
// 构造函数
// explicit表示该构造函数禁止隐式类型转换,即传入的参数必须是_Tp*类型
explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) {}
// 同类型拷贝构造函数,参数是另一个auto_ptr,但该auto_ptr要释放自己包含的指针
// 这是一个move(因此:千万不要使用类型为auto_ptr的容器)
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}
// 不同类型的拷贝构造函数
// 也是move
template<typename _Tpl>
auto_ptr(auto_ptr<_Tpl>& __a) throw() : _M_ptr(__a.release()) {}
// 同类型auto_ptr赋值操作符,__a释放自己包含的指针
// 本模板类用reset更新自己的指针为__a指针
auto_ptr& operator=(auto_ptr& __a) throw()
{
reset(__a.release());
return * this;
}
// 不同类型的auto_ptr赋值操作符
template<typename _Tpl>
auto_ptr& operator=(auto_ptr<_Tpl>& __a) throw()
{
reset(__a.release());
return * this;
}
// 析构函数
// 仅支持delete,不支持delete[]
~auto_ptr() { delete _M_ptr; }
// 重载dereference操作符
element_type& operator* const throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}
// 重载->操作符
element_type* operator->() cosnt throw()
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
// 获取成员指针函数
element_type* get() const throw() { return _M_ptr; }
// 获取成员指针函数,同时将成员指针清零
element_type* release() throw()
{
element_type* __tmp = _M_ptr;
_M_ptr = 0;
return _tmp;
}
// 释放成员指针所指对象,同时给成员指针赋新值
void reset(element_type* __p = 0) throw()
{
if(__p != _M_ptr)
{
delete _M_ptr;
_M_ptr = __p;
}
}
// 从auto_ptr_ref构造auto_ptr的构造函数
auto_ptr(auto_ptr_ref<element_type> __ref) throw() : _M_ptr(__ref._M_ptr) { }
// 从auto_ptr_ref的赋值操作符
// 因为这里要释放内存,所以要进行self-assignment检查
auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw()
{
if(__ref._M_ptr != this. get())
{
delete _M_ptr;
_M_ptr = __ref._M_ptr;
}
return * this.
}
// 本auto_ptr到其他任意类型auto_ptr_ref的conversation操作符重载
template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw()
{
return auto_ptr_ref<_Tp1>( this->release());
}
};
// 模拟auto_ptr的reference类型
// 可被一个传回auto_ptr值的函数赋值
template<typename _Tp1> struct auto_ptr_ref
{
_Tp1* _M_ptr;
explicit auto_ptr_ref(_Tpl* __p) : _M_ptr(__p) { }
};