C语音static、const、voilate和位运算

一 static

1 在C语言中

  1、加了static的全局变量和函数,对其他源文件隐藏(不能跨文件了);
  2、static修饰的函数内的局部变量,生存期为整个源程序运行期间,但作用域仍为函数内;
  3、static变量和全部变量一样,存在静态存储区,会默认初始化为0;

2 在C++中,多了以下两点

  1、声明静态成员变量,需要在类体外使用作用域运算符进行初始化;
  2、声明静态成员函数,在函数中不能访问非静态成员变量和函数。

3 静态数据成员可以成为成员函数的可选参数,普通数据成员不可以

  举例如下:

class base{
public:
	static int _staticVar;
	int _var;
	void foo1(int i=_staticVar);//正确,_staticVar为静态数据成员
	void foo2(int i=_var);//错误,_var为普通数据成员
};

4 静态数据成员的类型可以是所属类的类型,普通数据成员不可以

  普通数据成员的只能声明为所属类类型的指针或引用,举例如下:

class base{
public:
	static base _object1;//正确,静态数据成员
	base _object2;//错误
	base *pObject;//正确,指针
	base &mObject;//正确,引用
};

5 静态数据成员的值在const成员函数中可以被合法的改变

  这个特性,我不知道是属于标准c++中的特性,还是vc6自己的特性。举例如下:

class base{
public:
	base(){_i=0;_val=0;}
  mutable int _i;
  static int _staticVal;
  int _val;
  void test() const
  {//const 成员函数
  	_i++;//正确,mutable数据成员_staticVal++;//正确,static数据成员_val++;//错误
  }
};
int base::_staticVal = 0;

6 类的静态成员可以独立访问

  类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问,也就是说,无须创建任何对象实例就可以访问。非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
  在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。
  使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

7 静态数据成员的使用方法和注意事项如下:

  1、静态数据成员在定义或说明时前面加关键字static。
  2、静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:<数据类型><类名>::<静态数据成员名> = <值>
  这表明:
  (1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。
  (2) 初始化时不加该成员的访问权限控制符private,public等。
  (3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
  3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
  4、引用静态数据成员时,采用如下格式:<类名>::<静态成员名>

8 静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数指针来储存

  举例如下:

class base{
	static int func1();
	int func2();
};
int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针

9 静态成员函数不可以调用类的非静态成员

  在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
  C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这两者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。所以,它就无法访问自己类的非静态成员。

10 静态成员函数不可以同时声明为virtual、const、volatile函数

  举例如下:

class base{
	virtual static void func1();//错误
	static void func2() const;//错误
	static void func3() volatile;//错误
};

二 const

1 简介

  const分类:
  常变量:const 类型说明符 变量名;
  常引用:const 类型说明符 &引用名;
  常对象:类名 const 对象名;
  常成员函数:类名::fun(形参) const;
  常数组:类型说明符 const 数组名[大小];
  常指针:const 类型说明符* 指针名 ,类型说明符* const 指针名。
  首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、常数组(类型说明符 const 数组名[大小]),const与“类型说明符”或“类名”(其实类名是一种自定义的类型说明符)的位置可以互换。如:const int a=5;int const a=5;等同;类名 const 对象名const 类名 对象名等同。

2 const定义常量

  (1)const对象一旦创建后其值就不能再改变,所以const对象必须初始化(c++类中则不然)。否则之后就不能再进行赋值了。一如既往,初始值可以是任意复杂的表达式:

const int i = get_size(); // 正确: 运行时初始化
const int j = 42; //正确: 编译时初始化

  当以编译时初始化的方式定义一个const对象时, 编译器将在编译过程中把用到该变量的地方都替换成对应的值。
  (2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义。

3 const 对象作用域

  C标准中,const定义的常量是全局的,C++中视声明位置而定。
  在全局作用域定义的非const对象在整个程序中都可以访问,默认为extern code。
  const对象默认为文件的局部变量,要使const变量能够在其他的文件中访问,必须地指定它为extern code。

4 const引用

  可以把引用绑定到 const 对象上, 就像绑定到其他对象上一样, 我们称之为对常量的引用( reference to const)。 与普通引用不同的是, 对常M的引用不能被用作修改它所绑定的对象:

const int ci = 1024;
const int &rl = ci; // 正确: 引用及其对应的对象都是常量
r1 = 42;// 错误: rl 是对常量的引用
int &r2 = ci; //错误: 试图让一个非常量引用指向一个常量对象

  引用的类型必须与其所引用对象的类型一致, 但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值, 只要该表达式的结果能转换成引用的类型即可。尤其, 允许为一个常量引用绑定非常量的对象、 字面值, 甚至是个一般表达式;第二,对 const 的引用可能引用一个并非 const 的对象。

int i = 42;
const int &rl = i;//允 许 将 const int&绑定到一个普通 int 对象上
const int &r2 = 42;// 正确 : rl 是一个常量引用
const int &r3 = rl * 2;// 正确:i 3 是一个常量引用
int &r4 = rl 2;// 错误: r4 是一个普通的非常量引用

5 const和指针

  指针本身是一个对象, 它又可以指向另外一个对象。 因此, 指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。 用名词顶层const(top-level const) 表示指针本身是个常量, 而用名词底层 const ( low-level const) 表示指针所指的对象是一个常量。
  int const *ptr = &b;常量指针是指指向常量的指针,顾名思义,就是指针指向的是常量,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量;
  int *const ptr = &b指针常量是指指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。它指向的地址将伴其一生,直到生命周期结束。有一点需要注意的是,指针常量在定义时必须同时赋初值。从右向左把上述定义语句读作“ptr是指向 int 型对象的 const 指针”;
  const int *const ptr = &b;指向 const 对象的 const 指针,既不能修改ptr所指向对象的值,也不允许修改该指针的指向,从右向左阅读上述声明语句:“pi_ptr 首先是一个 const 指针,指向int类型的 const 对象”。

6 常量函数

  常量函数是C++对常量的一个扩展,它很好的确保了C++中类的封装性。在C++中,为了防止类的数据成员被非法访问,将类的成员函数分成了两类,一类是常量成员函数(也被称为观察着);另一类是非常量成员函数(也被成为变异者)。在一个函数的签名后面加上关键字const后该函数就成了常量函数。对于常量函数,最关键的不同是编译器不允许其修改类的数据成员。
  但是C++也允许我们在数据成员的定义前面加上mutable,以允许该成员可以在常量函数中被修改。

7 常量表达式

  常量表达式 ( const expression ) 是指值不会改变并且在编译过程就能得到计算结果的表达式。 显然, 字面值属于常量表达式, 用常量表达式初始化的 const 对象也是常量表达式。 C++语言中有几种情况下是要用到常量表达式的。
  C++11 新标准规定, 允许将变量声明为 constexpr 类型以便由编译器来验证变量的值是否是一个常量表达式。 声明为 constexpr 的变量一定是一个常量, 而且必须用常量表达式初始化:

constexpr int mf = 20; // 20 是常量表达式
constexpr int limit = mf + 1; // mf + 1 是常量表达式
constexpr int sz = size();  // 只有当 size 是一个 constexpr 函数时才是一条正确的声明语句

  尽管不能使用普通函数作为 constexpr 变量的初始值,新标准允许定义一种特殊的 constexpr 函数。这种函数应该足够简单以使得编译时就可以计算其结果,这样就能用 constexpr 函数去初始化 constexpr 变量了。

8 指针和constexpr

  在 constexpr 声明中如果定义了一个指针, 限定符 constexpr 仅对指针有效, 与指针所指的对象无关:

const int *p = nullptr; // p 是一个指向整型常量的指针
constexpr int *q = nullptr; // q 是一个指向整数的常量指针

  p和q的类型相差甚远,p是一个指向常量的指针,而q是一个常量指针,其中的关键在于constexpr把它所定义的对象置为了顶层const。

9 typedef和指针

typedef string *pstring;
const pstring cstr;
string *const cstr;

  声明const pstring时,const修饰的是pstring的类型,这是一个指针。因此,该声明语句应该是把cstr定义为指向string类型对象的。

10 const与迭代器

  const_iterator:只能用于读取容器内的元素,但不能改变其值。

11 示例

  在全局作用域定义的const对象:

// file_1.cc
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cc
extern const int bufSize; // uses bufSize from file_1,extern 标志着bufSize 是一个声明,所以没有初始化式
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...

  const引用则可以绑定到不同但相关的类型的对象或绑定到右值:

/*const 引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量:*/
int i = 42;
// legal for const references only
const int &r = 42;
const int &r2 = r + i;
/*同样的初始化对于非 const 引用却是不合法的,而且会导致编译时错误。其原因非常微妙,值得解释一下。
观察将引用绑定到不同的类型时所发生的事情,最容易理解上述行为。假如我们编写*/
double dval = 3.14;
const int &ri = dval;
/*编译器会把这些代码转换成如以下形式的编码:*/
int temp = dval;
const int &ri = temp;
// create temporary int from the double
// bind ri to that temporary
/*如果 ri 不是 const,那么可以给 ri 赋一新值。这样做不会修改 dval,而是修改了 temp。期望对 ri 的赋值会修改 dval 的程序员会发现 dval 并没89有被修改。仅允许 const 引用绑定到需要临时使用的值完全避免了这个问题,因为 const 引用是只读的。*/

  一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定:

const int max_files = 20; // max_files是常量表达式 
const int limit = max_files + 1; // limit是常量表达式 
int staff_size = 27; // staff_size不是常量表达式 
const int sz = get_size(); // sz不是常量表达式 
/*尽管staff_size的初始值是个字面值常量,但由于它的数据类型只是一个普通int而非const int,所以它不属于常量表达式。另一方面,尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。*/

12 const和static区别

  在C++中:
  1、const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
  2、static表示静态的。类的静态成员变量、静态成员函数是和类相关的,而不是和类的对象相关。即使没有没有具体的对象,也能调用类的静态成员函数、变量。static静态成员变量不能在类的内部初始化,只能在类内部声明、类外部初始化(且初始化不加static);const成员变量也不能在类内初始化,只能在构造函数的初始化列表进行初始化(即必须有构造函数)。const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static const。
  3、const成员函数主要目的是防止成员函数修改对象内容;static成员函数主要目的是作为类作用域的全局函数,且不能访问非静态成员变量,没有this指针。

13 const和预编译指令的区别

  与预编译指令相比,const修饰符有以下的优点:
  1、预编译指令只是对值进行简单的替换,不能进行类型检查;
  2、可以保护被修饰的东西,防止意外修改,增强程序的健壮性;
  3、编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
  4、宏定义的作用范围仅限于当前文件。 默认状态下,const对象只在文件内有效,当多个文件中出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。如果想在多个文件之间共享const对象,必须在变量定义之前添加extern关键字(在声明和定义时都要加)。

14 将Const类型转化为非Const类型的方法

  采用const_cast 进行转换。该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。 常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

15 使用const的一些建议

  要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
  要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
  在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
  const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
  不要轻易的将函数的返回值类型定为const;
  除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
  任何不会修改数据成员的函数都应该声明为const 类型。

16 补充重要说明

   类内部的常量限制:使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。
  如何初始化类内部的常量:一种方法就是static 和 const 并用,在外部初始化,例如:

class A{
public:
	A() {}
private:
	static const int i; //注意必须是静态的!
}; 
const int A::i=3;

  另一个很常见的方法就是初始化列表:

class A { 
public:
	A(int i=0):test(i) {}
private:
	const int i;
};

  还有一种方式就是在外部初始化。
  如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个volatile类类型的。
  new返回的指针必须是const类型的。

三 类相关CONST

1 const和成员变量

  const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

class A
{const int nValue; //成员常量不能被修改A(int x): nValue(x) { } ; //只能在初始化列表中赋值
}

  const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。
  对于非内部数据类型的输入参数,因该将“值传递”的方式改为“const引用传递”,目的是为了提高效率。例如,将void Func(A a)改为void Func(const A &a);
  对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int &x)。

2 const和成员函数

class A
{void function()const; //常成员函数,它不改变对象的成员变量,也不能调用类中任何非const成员函数。
} 

  const成员不能改变其所操作的对象的数据成员,const必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。
  在普通的非const成员函数中,this的类型是一个指向类类型的const指针,可以改变this所指向的值,但不能改变this所保存的地址。
  在const成员函数中this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。
  不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用。
  const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
  const对象的成员是不能修改的,而通过指针维护的对象确是可以修改的;
  const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。
  C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。

3 const与成员函数重载

Screen& display(std::ostream &os)
{
	do_display(os); return *this;
}
const Screen& display(std::ostream &os) const
{
	do_display(os); return *this;
}

4 const修饰类对象/对象指针/对象引用

  const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
  const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
  例如:

class AAA
{ 
	void func1(); 
	void func2() const; 
} 
const AAA aObj; 
aObj.func1(); //×
aObj.func2(); //正确
const AAA* aObj = new AAA(); 
aObj-> func1(); //×
aObj-> func2(); //正确

四 volatile

  一般说来,volatile用在如下的几个地方:1、中断服务程序中修改的供其它程序检测的变量需要加volatile;2、多任务环境下各任务间共享的标志应该加volatile;3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。
  1、volatile表示“易变的”,即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况):被volatile修饰的对象,编译器不会对这个对象的操作进行优化。
  2、一个对象可以同时被constvolatile修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。
  3、被volatile这个关键字修饰的变量在每次访问的时候都要去相应内存地址去找,因为随时可能被修改。被const修饰只能说明这个不能被程序员修改,但可能被系统所修改。
  constvolatile是类型修饰符,语法类似,用于变量或函数参数声明,也可以限制非静态成员函数。用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问,阻止编译器调整操作volatile变量的指令顺序。当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
  volatile 指针: 1、修饰由指针指向的对象、数据是 const 或 volatile 的:const char* cpch; volatile char * vpch;;2、指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:char* const pchc; char* volatile vchc;
  注意:(1) 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。(2)除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。(3)C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

五 C++局部静态对象的内存创建时间

  在程序执行到该对象的定义处时,创建对象并调用相应的构造函数!
  如果在定义对象时没有提供初始指,则会暗中调用默认构造函数,如果没有默认构造函数,则自动初始化为0。
  如果在定义对象时提供了初始值,则会暗中调用类型匹配的带参的构造函数(包括拷贝构造函数),如果没有定义这样的构造函数,编译器可能报错!
  直到main()结束后才会调用析构函数!
  转自:《高质量程序设计指南》

一 取出每一个bit位的值

  取出每一个bit位的值,是0或者1。

#include <stdio.h>

int main()
{	
	char byData = 0x36; //0110 1100

	int n0, n1, n2, n3, n4, n5, n6, n7;
	n0 = (byData & 0x01) == 0x01 ? 1 : 0;
	n1 = (byData & 0x02) == 0x02 ? 1 : 0;
	n2 = (byData & 0x04) == 0x04 ? 1 : 0;
	n3 = (byData & 0x08) == 0x08 ? 1 : 0;
	n4 = (byData & 0x10) == 0x10 ? 1 : 0;
	n5 = (byData & 0x20) == 0x20 ? 1 : 0;
	n6 = (byData & 0x40) == 0x40 ? 1 : 0;
	n7 = (byData & 0x80) == 0x80 ? 1 : 0;
	printf("0-%d, 1-%d, 2-%d, 3-%d, 4-%d, 5-%d, 6-%d, 7-%d\n", n0, n1, n2, n3, n4, n5, n6, n7);
	return 0;
}

二 算术移位和逻辑移位

  算术移位逻辑移位主要是针对右移而言:算数移位用符号位作为填充,逻辑移位不填充直接补零。
  而左移时总是移位,然后补零。
  例如:
  1、127的补码:0111 1111(127>>1)
  右移一位: 0011 1111 -> 原码同补码一样 对应十进制:63
  右移二位: 0001 1111 -> 原码同补码一样 对应十进制:31
  右移三位: 0000 1111 -> 原码同补码一样 对应十进制:15
  右移四位: 0000 0111 -> 原码同补码一样 对应十进制:7
  右移五位: 0000 0011 -> 原码同补码一样 对应十进制:3
  右移六位: 0000 0001 -> 原码同补码一样 对应十进制:1
  右移七位: 0000 0000 -> 原码同补码一样 对应十进制:0
  右移八位: 0000 0000 -> 原码同补码一样 对应十进制:0
  2、-128的补码:1000 0000
  右移一位: 1100 0000 -> 这个补码对应的原码为:1100 0000 对应十进制:-64
  右移二位: 1110 0000 -> 这个补码对应的原码为:1010 0000 对应十进制:-32
  右移三位: 1111 0000 -> 这个补码对应的原码为:1001 0000 对应十进制:-16
  右移四位: 1111 1000 -> 这个补码对应的原码为:1000 1000 对应十进制:-8
  右移五位: 1111 1100 -> 这个补码对应的原码为:1000 0100 对应十进制:-4
  右移六位: 1111 1110 -> 这个补码对应的原码为:1000 0010 对应十进制:-2
  右移七位: 1111 1111 -> 这个补码对应的原码为:1000 0001 对应十进制:-1
  右移八位: 1111 1111 -> 这个补码对应的原码为:1000 0001 对应十进制:-1

三 移位与乘除法的关系

  移位实现的乘除法比直接乘除的效率高很多。

a=a*4;
b=b/4;

  可以改为:

a=a<<2;
b=b>>2;

  说明:除2 = 右移1位,乘2 = 左移1位,除4 = 右移2位,乘4 = 左移2位,除8 = 右移3位,乘8 = 左移3位。通常如果需要乘以或除以2的n次方,都可以用移位的方法代替。
  大部分的C编译器,用移位的方法得到代码比调用乘除法子程序生成的代码效率高。
  实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,如: a = a ∗ 9 a=a*9 a=a9,分析 a ∗ 9 a*9 a9可以拆分成 a ∗ ( 8 + 1 ) a*(8+1) a(8+1) a ∗ 8 + a ∗ 1 a*8+a*1 a8+a1,因此可以改为: a = ( a < < 3 ) + a a=(a<<3)+a a=(a<<3)+a a = a ∗ 7 a=a*7 a=a7,分析 a ∗ 7 a*7 a7可以拆分成 a ∗ ( 8 − 1 ) a*(8-1) a(81) a ∗ 8 − a ∗ 1 a*8-a*1 a8a1,因此可以改为: a = ( a < < 3 ) − a a=(a<<3)-a a=(a<<3)a
  关于除法读者可以类推,此略。

四 什么样的数据类型可以直接移位

  charshortintlongunsigned charunsigned shortunsigned intunsigned long都可以进行移位操作,而doublefloatboollong double则不可以进行移位操作。

五 有符号数据类型的移位操作

  对于charshortintlong这些有符号的数据类型:
  对负数进行左移:符号位始终为1,其他位左移;
  对正数进行左移:所有位左移,即<<,可能会变成负数;
  对负数进行右移:取绝对值,然后右移,再取相反数;
  对正数进行右移:所有位右移,即>>。

六 无符号数据类型的移位操作

  对于unsigned charunsigned shortunsigned intunsigned long这些无符号数据类型:没有特殊要说明的,使用<<和>>操作符就OK了。

七 C/C++移位运算符出界后的结果是不可预期的

  以前看到C++标准上说,移位运算符(<<、>>)出界时的行为并不确定:The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.
  Intel CPU的移位运算有关。

#include <stdio.h>
void main()
{
   unsigned int i,j;
   i=35;
 
   //为什么下面两个左移操作结果不一样?
   j=1<<i;  // j为8
   j=1<<35; // j为0
}

  原因是这样的:i=35;j=1<<i;这两句在VC没有做优化的情况下,将被编译成下面的机器指令:

mov dword ptr [i],23h
mov eax,1
mov ecx,dword ptr [i]
shl eax,cl
mov dword ptr [j],eax

  在shl一句中,eax=1,cl=35。而Intel CPU执行shl指令时,会先将cl与31进行and操作,以限制左移的次数小于等于31。因为35 & 31 = 3,所以这样的指令相当于将1左移3位,结果是8。
  而j=1<<35;,一句是常数运算,VC即使不做优化,编译器也会直接计算1<<35的结果。VC编译器发现35大于31时,就会直接将结果设置为0。这行代码编译产生的机器指令是:mov dword ptr [j],0
  对上面这两种情况,如果把VC编译器的优化开关打开(比如编译成Release版本),编译器都会直接将结果设置为0。
  所以,在C/C++语言中,移位操作不要超过界限,否则,结果是不可预期的。
  下面是Intel文档中关于shl指令限制移位次数的说明:The destination operand can be a register or a memory location. The count operand can be an immediate value or register CL. The count is masked to 5 bits, which limits the count range to 0 to 31. A special opcode encoding is provided for a count of 1.

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值