C++基础知识笔记

1.public,private,protected,friend区别
private和public的作用是让编译器帮你检查某些模块是否使用了他没权限使用的模块,也就是生成可执行代码的时候做权限检查。比如,公司里各个部门有自己私有的信息,财务部可以看所有员工工资,而销售部不可以,普通员工也不可以。
可不可以访问都是人为规定的,而且在写代码的时候程序是不会执行的,因此需要在生成代码的时候做一些检查,就像语法错误在编译的时候被检查出来一样,因为人规定了代码该如何写。
访问控制也是在编译的时候检查,c++采用了private,publicprotected,以及friend来限制访问权限。
private的意思是指类的内部变量或者函数是私有的,在类之外包括继承类就不可见,像魔术师的道具;
public是指类的内部变量是外部可见的,像魔术师的表演;
protected是指除了本类和继承类之外不可见,像魔术师希望徒弟使用道具,就得让其徒弟看见道具,但是是一种受保护的权限;
friend是指特定指出哪些类或者模块可以看见本类的私有(private以及protected)成员,这就是魔术师信得过的朋友可以让他看见一些幕后。
使用好这个规则,只要程序中出现了越权访问的代码,编译的时候就会被检查出来,以保证安全。
举例:
class moshushi//魔术师类
{
friend class daoyan;//导演可以知道魔术师背后的秘密
public://所有人都可以看见的行为以及物品
int jinchang();//进场
int biaoyan();//表演
int tuichang();//退场
int shou, yifu, maozi;//手,衣服,帽子
protected://徒弟可以知道的
int zhaotuo();//找个托
int gangsi;//钢丝
private://只有自己和friend可以知道
int zuobi();//作弊
int yaoshui, tezhizhuozi;//药水,特制桌子
};

class tudi: public moshushi魔术师徒弟
{
//自动有了魔术师的public以及protected
};

class daoyan导演
{
int func(){
//可以访问到魔术师对象的私有域
}
//...
};

class guanzhong观众
{
//不能访问魔术师对象的私有域
//...
};

原文:http://zhidao.baidu.com/question/212719721.html

2.最基本的位运算
//=== 1. and运算 ===( & )
  and运算通常用于二进制取位操作,例如一个数 and 1的结果就是取二进制的最末位。这可以用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数.
  相同位的两个数字都为1,则为1;若有一个不为1,则为0。
  00111
  11100
  (&或者and)
  ----------------
  00100
//=== 2. or运算 ===( | )
  or运算通常用于二进制特定位上的无条件赋值,例如一个数or 1的结果就是把二进制最末位强行变成1。如果需要把二进制最末位变成0,对这个数or 1之后再减一就可以了,其实际意义就是把这个数强行变成最接近的偶数。
  相同位只要一个为1即为1。
  00111
  11100
  (|或者or)
  ----------------
  11111
//=== 3. xor运算 ===( ^ )
  异或的符号是⊕。
  xor运算通常用于对二进制的特定一位进行取反操作,因为异或可以这样定义:0和1异或0都不变,异或1则取反。
  xor运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即(a xor b) xor b = a。xor运算可以用于简单的加密,比如我想对我MM说1314520,但怕别人知道,于是双方约定拿我的生日19880516作为密钥。1314520 xor 19880516 = 20665500,我就把20665500告诉MM。MM再次计算20665500 xor 19880516的值,得到1314520,于是她就明白了我的企图。
  下面我们看另外一个东西。定义两个符号#和@(xor=⊕),这两个符号互为逆运算,也就是说(x # y) @ y = x。现在依次执行下面三条命令,结果是什么?
  相同位不同则为1,相同则为0。
  00111
  11100
  (^或者xor)
  ----------------
  11011
//=== 4. not运算 ===( ~ )
  not运算的定义是把内存中的0和1全部取反。使用not运算时要格外小心,你需要注意整数类型有没有符号。如果not的对象是无符号整数(不能表示负数),那么得到的值就是它与该类型上界的差,因为无符号类型的数是用00到$FFFF依次表示的。下面的两个程序(仅语言不同)均返回65435。
  var
  a:word;
  begin
  a:=100;
  a:=not a;
  writeln(a);
  end.
  #include <stdio.h>
  int main()
  {
    unsigned short a=100;
    a = ~a;
    printf( "%d\n", a );
    return 0;
  }
  如果not的对象是有符号的整数,情况就不一样了,稍后我们会在“整数类型的储存”小节中提到。
//=== 5. shl运算 ===( << )
  a shl b就表示把a转为二进制后左移b位(在后面添b个0)。例如100的二进制为1100100,而110010000转成十进制是400,那么100 shl 2 = 400。可以看出,a shl b的值实际上就是a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。
  通常认为a shl 1比a * 2更快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。
  定义一些常量可能会用到shl运算。你可以方便地用1 shl 16 - 1来表示65535。很多算法和数据结构要求数据规模必须是2的幂,此时可以用shl来定义Max_N等常量。
//=== 6. shr运算 ===( >> )
  和shl相似,a shr b表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)。我们也经常用shr 1来代替div 2,比如二分查找、堆的插入操作等等。想办法用shr代替除法运算可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运算,效率可以提高60%。

///////////////////////////////////////////////
//位运算的简单应用  
有时我们的程序需要一个规模不大的Hash表来记录状态。比如,做数独时我们需要27个Hash表来统计每一行、
每一列和每一个小九宫格里已经有哪些数了。此时,我们可以用27个小于2^9的整数进行记录。
例如,一个只填了2和5的小九宫格就用数字18表示(二进制为000010010),而某一行的状态为511则表示这一行已经填满。
需要改变状态时我们不需要把这个数转成二进制修改后再转回去,而是直接进行位操作。在搜索时,
把状态表示成整数可以更好地进行判重等操作。这道题是在搜索中使用位运算加速的经典例子。以后我们会看到更多的例子。
  下面列举了一些常见的二进制位的变换操作。
功能 | 示例 | 操作
  去掉最后一位 |   (101101->10110) |   x shr 1
  在最后加一个0  |   (101101->1011010) |   x shl 1
  在最后加一个1  |   (101101->1011011) |   x shl 1+1
  把最后一位变成1  |   (101100->101101) |   x or 1
  把最后一位变成0  |   (101101->101100) |   x or 1-1
  最后一位取反 |   (101101->101100) |   x xor 1
  把右数第k位变成1 |   (101001->101101,k=3) |   x or (1 shl (k-1))
  把右数第k位变成0 |   (101101->101001,k=3) |   x and not (1 shl (k-1))
  右数第k位取反  |   (101001->101101,k=3) |   x xor (1 shl (k-1))
  取末三位 |   (1101101->101) |   x and 7
  取末k位  |   (1101101->1101,k=5) |   x and (1 shl k-1)
  取右数第k位  |   (1101101->1,k=4) |   x shr (k-1) and 1
  把末k位变成1  |   (101001->101111,k=4) |   x or (1 shl k-1)
  末k位取反 |   (101001->100110,k=4) |   x xor (1 shl k-1)
  把右边连续的1变成0 |   (100101111->100100000) |   x and (x+1)
  把右起第一个0变成1 |   (100101111->100111111) |   x or (x+1)
  把右边连续的0变成1 |   (11011000->11011111) |   x or (x-1)
  取右边连续的1 |   (100101111->1111) |   (x xor (x+1)) shr 1
  去掉右起第一个1的左边 |   (100101000->1000)     |   x and (x xor (x-1))
  最后这一个在树状数组中会用到。
  Pascal和C中的16进制表示   
Pascal中需要在16进制数前加$符号表示,C中需要在前面加0x来表示。这个以后我们会经常用到。
原文:http://www.cnblogs.com/o8le/archive/2011/10/16/2214280.html

3.静态变量和静态函数、静态成员变量和静态成员函数
//数据成员可以分静态变量、非静态变量两种. 
静态成员:类中的成员加入static修饰符,即是静态成员.可以直接使用类名+静态成员名访问此静态成员,因为静态成员存在于内存,非静态成员需要实例化才会分配内存,所以静态成员不能访问非静态的成员..因为静态成员存在于内存,所以非静态成员可以直接访问类中静态的成员.

非静态成员:所有没有加Static的成员都是非静态成员,当类被实例化之后,可以通过实例化的类名进行访问..非静态成员的生存期决定于该类的生存期..而静态成员则不存在生存期的概念,因为静态成员始终驻留在内容中..

//一个类中可以包含静态成员和非静态成员,类中也包括静态构造函数和非静态构造函数

分两个方面来总结,第一方面主要是相对于面向过程而言,即在这方面不涉及到类,第二方面相对于面向对象而言,主要说明static在类中的作用。

////////////////////////////////////////////////////////////////////////////////////////////////////////
一、在面向过程设计中的static关键字
1、静态全局变量
定义:在全局变量前,加上关键字 static 该变量就被定义成为了一个静态全局变量。
特点:
  A、该变量在全局数据区分配内存。
  B、初始化:如果不显式初始化,那么将被隐式初始化为0(自动变量是随机的,除非显式地初始化)。
  C、该变量只在本源文件可见,严格的讲应该为定义之处开始到本文件结束。
例(摘于C++程序设计教程---钱能主编P103):         //file1.cpp
//Example 1
#include <iostream.h>
void fn();
static int n; //定义静态全局变量
void main()
{
n=20;
cout < <n < <endl;
fn();
}

void fn()
{
n++;
cout < <n < <endl;
}

  D、文件作用域下声明的const的常量默认为static存储类型。

静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:
代码区
全局数据区
堆区
栈区
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。

细心的读者可能会发现,Example 1中的代码中将
static int n; //定义静态全局变量
改为:
int n; //定义全局变量程序照样正常运行。
的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
静态全局变量不能被其它文件所用;(好像是区别extern的)
其它文件中可以定义相同名字的变量,不会发生冲突;

您可以将上述示例代码改为如下:
//Example 2
//File1
#include <iostream.h>
void fn();
static int n; //定义静态全局变量(只能在本文件中使用)
void main()
{
n=20;
cout < <n < <endl;
fn();
}

//File2
#include <iostream.h>
extern int n;(可在别的文件中引用这个变量)
void fn()
{
n++;
cout < <n < <endl;
}
编译并运行Example 2,您就会发现上述代码可以分别通过编译,但link时出现错误。
试着将
static int n; //定义静态全局变量
改为
int n; //定义全局变量
再次编译运行程序,细心体会全局变量和静态全局变量的区别。

2、静态局部变量
定义:在局部变量前加上static关键字时,就定义了静态局部变量。我们先举一个静态局部变量的例子,如下:
//Example 3
#include <iostream.h>
void fn();
void main()
{
fn();
fn();
fn();
}
void fn()
{
static n=10;
cout < <n < <endl;
n++;
}
  通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
  但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
  静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。

特点:
  A、该变量在全局数据区分配内存。
  B、初始化:如果不显式初始化,那么将被隐式初始化为0,以后的函数调用不再进行初始化。
  C、它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或 语句块结束时,其作用域随之结束。

3、静态函数(注意与类的静态成员函数区别)
定义:在函数的返回类型前加上static关键字,函数即被定义成静态函数。
特点:
  A、静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。   
静态函数的例子:
//Example 4
#include <iostream.h>
static void fn();//声明静态函数
void main()
{
fn();
}
void fn()//定义静态函数
{
int n=10;
cout < <n < <endl;
}
定义静态函数的好处:静态函数不能被其它文件所用;
其它文件中可以定义相同名字的函数,不会发生冲突;


////////////////////////////////////////////////////////////////////////////////////////////////////////

二、面向对象的static关键字(类中的static关键字)
1、静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。
//Example 5
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
void GetSum();

private:
int a,b,c;
static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员

Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c;
}

void Myclass::GetSum()
{
cout < <"Sum=" < <Sum < <endl;
}

void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
M.GetSum();
}
//可以看出,静态数据成员有以下特点:
对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。
所以,静态数据成员的值对每个对象都是一样的,它的值可以更新; 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。
在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员; 静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;

//静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
<数据类型><类名>::<静态数据成员名>=<值>
//类的静态数据成员有两种访问形式:
<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;

//同全局变量相比,使用静态数据成员有两个优势:
静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
2、静态成员函数
   与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。
/////静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。/////
普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。
/////从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。/////
下面举个静态成员函数的例子。
//Example 6
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
static void GetSum();/声明静态成员函数
private:
int a,b,c;
static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员

Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
}

void Myclass::GetSum() //静态成员函数的实现
{
// cout < <a < <endl; //错误代码,a是非静态数据成员
cout < <"Sum=" < <Sum < <endl;
}

void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
Myclass::GetSum();
}

//关于静态成员函数,可以总结为以下几点:
//出现在类体外的函数定义不能指定关键字static;
//静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
//非静态成员函数可以任意地访问静态成员函数和静态数据成员;
//静态成员函数不能访问非静态成员函数和非静态数据成员;
//由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
//调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,
//当同一类的所有对象使用一个量时,对于这个共用的量,可以用静态数据成员变量,这个变量对于同一类的所有的对象都取相同的值。静态成员变量只能被静态成员函数调用。 静态成员函数也是由同一类中的所有对象共用。只能调用静态成员变量和静态成员函数。

原文:http://zzqh007.blog.163.com/blog/static/44434847201010595445770/

4.static—非面向对象
只讲在普通情况下,即不涉及类的情况下: 
1.static 表明修饰的函数的作用域是本文件.
2.通常我们将只会在本文件中使用的函数定义为static,这样可以避免可能的命名冲突.同时将带来一个好处,那就是 "错误局部化 ",便于查错.
3.正因为我们只在这一个文件内使用,所以我们通常在CPP文件的头部声明,而不会把它放在在H文件中.
4.我们通常不会#include CPP文件,(有时在碰到模板时例外),而是#include.H 文件.

举例来说在SDK情况下,我有一个父窗口,下面管理着20个子窗口,在子窗口的WNDPROC中都一个函数叫OnInitDiaog()处理相应的WM_INITDIALOG消息.每个子窗口的WNDPROC都在一个单独的CPP文件中.如果我声明其为static,则所有的函数都可以叫做OnInitDialog(),否则就得改成Dialog1_OnInitDialog(), Dialog2_OnInitDialgo().... Dialog20_OnInitDialog(),你说烦不烦?


其次,
假设你写了一个LIB(假设是STATIC LIB),你希望你的用户用f1(),f2()两个函数,因此你给了他们一个头文件,里面只有这两个函数的声明,你想我把其他的细节都黑箱化的,用户看不到,应该不会有问题了,但在你的LIB中有一个g(),它只能被f1()或f2()调用.但用户不知道,因为他拿到头文件中没有,而LIB是个二进制文件.更何况你也不希望他知道你的g()函数,因为那完全是私有的.

用户使用你的LIB,在他的程序中他写了一个函数g(),他和你一样不用static,他编译无误,链接时出错了,因为有两个g(),用户十分纳闷,他只看见了自己的g().

采用static就可避免上述情况.

原文:http://topic.csdn.net/t/20020910/23/1013137.html

5.嵌入式编程中关于const,static,extern,volatile的用法
////////////////////////////////////////
1.const的用法:
//为什么使用const?
采用符号常量写出的代码更容易维护;指针常常是边读边移动,而不是边写边移动;许多函数参数是只读不写的。const最常见用途是作为数组的界和switch分情况标号(也可以用枚举符代替)

//用法1:常量
取代了C中的宏定义,声明时必须进行初始化。const限制了常量的使用方式,并没有描述常量应该如何分配。如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。―《C++ Program Language》
用const声明的变量虽然增加了分配空间,但是可以保证类型安全。C标准中,const定义的常量是全局的,C++中视声明位置而定。

//用法2:指针和常量
使用指针时涉及到两个对象:该指针本身和被它所指的对象。将一个指针的声明用const“预先固定”将使那个对象而不是使这个指针成为常量。要将指针本身而不是被指对象声明为常量,必须使用声明运算符*const。所以出现在 * 之前的const是作为基础类型的一部分:
char *const cp; //到char的const指针
(后两个声明是等同的)
char const *pc1; //到const char的指针
const char *pc2; //到const char的指针
从右向左读的记忆方式:
cp is a const pointer to char.
pc2 is a pointer to const char.

//用法3:const修饰函数传入参数
将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。
通常修饰指针参数和引用参数:
void Fun(const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数

//用法4:修饰函数返回值
可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。

//用法5:const修饰成员函数
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。

////////////////////////////////////////
2.static的用法:
//全局静态变量怎么用??
所谓的全局静态变量只在当前程序中有效。比如在a.c中定义了
static int i;
则只有a.c中的函数可以访问i,其它程序模块的无法直接访问这个变量,但可以通过a.c中所定义的函数间接访问。比如在a.c中定义两个函数:
int get_i()
{
return i;
};

int set_i( int n )
{
i=n;
}

以此获得及重新设置i的值。这在c环境下是一种较好的模拟C++风格的实现方法。因为你可以将a.c看作一个类,i看作a的成员,而get_i()和set_i()则看作这个类的成员函数。
静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。
静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。

//用法1:
函数内部声明的static变量,可作为对象间的一种通信机制
如果一局部变量被声明为static,那么将只有唯一的一个静态分配的对象,它被用于在该函数的所有调用中表示这个变量。这个对象将只在执行线程第一次到达它的定义使初始化。

//用法2:局部静态对象
对于局部静态对象,构造函数是在控制线程第一次通过该对象的定义时调用。在程序结束时,局部静态对象的析构函数将按照他们被构造的相反顺序逐一调用,没有规定确切时间。

//用法3:静态成员和静态成员函数
如果一个变量是类的一部分,但却不是该类的各个对象的一部分,它就被声明成为是一个static静态成员。一个static成员只有唯一的一份副本,而不像常规的非static成员那样在每个对象里各有一份副本。同理,一个需要访问类成员,而不需要针对特定对象去调用的函数,也被称为一个static成员函数。
类的静态成员函数只能访问类的静态成员(变量或函数)。

////////////////////////////////////////
3.extern的用法:
extern可以声明其他文件内定义的变量。在一个程序里,一个对象只能定义一次,它可以有多个声明,但类型必须完全一样。如果定义在全局作用域或者名字空间作用域里某一个变量没有初始化,它会被按照默认方式初始化。
将变量或函数声明成外部链接,即该变量或函数名在其它函数中可见。被其修饰的变量(外部变量)是静态分配空间的,即程序开始时分配,结束时释放。
在C++中,还可以指定使用另一语言链接,需要与特定的转换符一起使用。
extern “C” 声明语句
extern “C” { 声明语句块 }

////////////////////////////////////////
4.volatile的用法:
类型修正符(type-modifier),限定一个对象可被外部进程(操作系统、硬件或并发进程等)改变。volatile与变量连用,可以让变量被不同的线程访问和修改。声明时语法:int volatile vInt;

除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
注意:可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。

volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:

bit bFlag="0";

int main(void)
{
...
while (1)
{
if (bFlag) dosomething();
}
}

/* 中断程序*/
void ISR(void)
{
bFlag=1;
}

程序的本意是希望ISR中断产生时,在main当中调用dosomething函数,但是,由于编译器判
断在main函数里面没有修改过bFlag,因此
可能只执行一次对从bFlag到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“bFlag副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中bFlag也应该如此说明。

volatile是一个限定符,也称为keyword或描述符,"volatile 关键字指示字段可由操作系统、硬件或并发执行的线程在程序中进行修改。"

当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。


一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
声明方式为  volatile declaration

备注
系统总是在volatile对象被请求的那一刻读取其当前值,即使上一条指令从同一对象请求值。而且,该对象的值在赋值时立即写入。

volatile 修饰符通常用于由多个线程访问而不使用 lock 语句来序列化访问的字段。使用 volatile 修饰符能够确保一个线程检索由另一线程写入的最新值。

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。

volatile 的含义
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:

1 不会在两个***作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器 自己无法知道,volatile就是告诉编译器这种情况。

2 不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) ...
if的条件不会当作无条件真。

3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值***作,然而对Memory Mapped IO的处理是不能这样优化的。

一个网友说:volatile的意思是什么?
很多时候,全局变量不一定是全局的,在多线程环境下可能产生微妙的错误,很有可能编译器为了优化,而把一个全局变量放入寄存器里。volatile修饰符就是明确告诉编译器,你他妈不准把这个变量优化到寄存器上,只能放内存里。


//-------------------const应用--------------------------
一、对于基本声明
const int r=100;//标准const变量声明加初始化,编译器经过类型检查后直接用100在编译时替换。

二、对于指针
1. int x=10; const int *r=&x; //指针指向的内容是常量,r指向的内容不能够通过r改变,但如果是非const,内容可以通过自己改变,而且r指针可以改变,可以指向其它的整形.
// *r=*r+1;NO
// x++;YES
// r=&y;YES

2. int const *r=&x; 与1完全相同

3. int * const r=&x; //指针指向是常量,不能修改去指向其它内容,但指向的内容可以修改
//r=&y;NO
//*r=*r+1;YES
//x++;YES

4.const int * const r=&x; //综合1、3用法,r是一个指向常量的常量型指针,指针指向不能改变,指针内容不能改变,内容可以自身改变
//r=&y;NO
//*r=*r+1;NO
//x++;YES

三、对于类型检查
以把非const对象赋予const指针,这样就不能改变.但是不能把const赋给非const,除非先强制转换
const int x=100; int *p=(int*)&x; *p++;

四、对于函数
1.void Fuction1(const int r); //此处为参数传递const值,意义是变量初值不能被函数改变

2.const int Fuction1 (int); //此处返回const值,意思指返回的原函数里的变量的初值不能被修改,但是函数按值返回的这个变量被制成副本,能不能被修改就没有了意义,它可以被赋给任何的const或非const类型变量,完全不需要加上这个const关键字。

3.Class CX; //内部有构造函数,声明如CX(int r =0)
CX Fuction1 () { return CX(); }
const CX Fuction2 () { return CX(); }
Fuction1() = CX(1); //没有问题,可以作为左值调用
Fuction2() = CX(1); //编译错误,const返回值禁止作为左值调用。

4.函数中指针的const传递和返回:
int F1 (const char *pstr); //作为传递的时候使用const修饰可以保证不会通过这个指针来修改传递参数的初值
const char *F2();//意义是函数返回的指针指向的对象是一个const对象,它必须赋给一个同样是指向const对象的指针
const char * const F3(); //比上面多了一个const,这个const的意义只是在他被用作左值时有效,它表明了这个指针除了指向const对象外,它本身也不能被修改,所以就不能当作左值来处理。
五、对于类
1.首先,对于const的成员变量,只能在构造函数里使用初始化成员列表来初始化,试图在构造函数体内进行初始化const成员变量会引起编译错误。初始化成员列表形如:
X:: X ( int ir ): r(ir) {} //假设r是类X的const成员变量
注意:类的构造和析构函数都不能是const函数。

2.建立了一个const成员函数,但仍然想用这个函数改变对象内部的数据。(函数不能修改类的数据成员)
//假如有一个叫做X的类,它有一个int成员变量r,我们需要通过一个const成员函数f( )来对这个r进行++r操作,代码如下
void X::f( ) const
{ (const_cast(this)) -> ++r; } //通过this指针进行类型强制转换实现


//-------------------STATIC应用--------------------------
对于一个完整的程序,内存中的分布情况:
==========
| 代码区 |
------------------
| 全局数据区 |
------------------
| 堆区 |
-----------------
| 栈区 |
==========
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区,全局变量和static变量放在全局数据区

static的作用主要有以下3个:
1、扩展生存期;
2、限制作用域;
3、唯一性

STATIC:
一、面向过程设计中的static
1、[静态全局变量] //在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。
静态全局变量有以下特点:
1)该变量在全局数据区分配内存;
2)未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
3)静态全局变量在声明它的整个文件都是可见的,而在文件之外(extern)是不可见的; 
定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
1)静态全局变量不能被其它文件所用;
2)其它文件中可以定义相同名字的变量,不会发生冲突;

2、[静态局部变量] 在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
1)该变量在全局数据区分配内存;
2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作 用域随之结束;

3、静态函数
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
定义静态函数的好处:
1)静态函数不能被其它文件所用;
2)其它文件中可以定义相同名字的函数,不会发生冲突;

二、面向对象的static关键字(类中的static关键字)
1、静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。

静态数据成员有以下特点:
1)而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数 据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。
2)静态数据成员存储在全局数据区,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;

同全局变量相比,使用静态数据成员有两个优势:
1)静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
2)可以实现[信息隐藏]。静态数据成员可以是private成员,而全局变量不能;

2、静态成员函数
它为类的全部服务而不是为某一个类的具体对象服务。与普通函数相比,静态成员函数由于不是与任何的 对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。

关于静态成员函数,可以总结为以下几点:
1)出现在类体外的函数定义不能指定关键字static;
2)静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
3)非静态成员函数可以任意地访问静态成员函数和静态数据成员;
4)静态成员函数不能访问非静态成员函数和非静态数据成员

//-------------------EXTERN应用--------------------------
1 基本解释
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。通过这种行为它告诉编译器:该变量/函数的定义已经存在在某个地方了,让编译器到其他的模块去寻找它的定义。
另外,extern也可用来进行链接指定。

2. extern “C”
使用extern“C”主要是因为C++语言在编译的时候为了实现多态,会将函数名和函数结合起来形成另外一种函数名(总之就是说编译后的函数名与你之前自己声明时的函数名会不一样),而C语言中无多态的概念当然也就不会有这种奇异的名字变化问题。这是问题就出现了,当你要在C++中调用C函数时,由于名字的不同,所以它会找不到所调用的这个函数的定义,因而会出错。
为了解决这一C与C++的矛盾冲突,就有了extern "C'。
原文:http://apps.hi.baidu.com/share/detail/33706193

6.返回值:值,指针,引用
//-----------------------指针 or 引用-----------------
当函数返回指针的时候,返回的是一个地址;
比如
int *fun(int *p)
{
(*p)++;
return p;
}
当函数返回一个引用时,返回的是也一个地址;比如
int& func(int& p)
{
return p;
}
当返回引用的时候,函数可以作为左值来使用,也就是说可以实现链式表达式


指针或引用做为返回值时,必须得是当函数返回(结束后)依然存在在的值,所以不能是函数内部变量

//1) a.c
#include <stdio.h>
#include <string.h>

int *func(int *p) //因为p是从主调函数传过来的指针,所以当func结束时依然存在,所以
{
(*p)++;
return p; //OK: 可以做为返回值
}

int main()
{
int i=10;
int *p=&i;

printf("%d\n", *(func(p)));
return 0;
}

//2)a.cpp
#include <stdio.h>

int& func(int& p) //因为p是从主调函数传过来的引用,所以当func结束时依然存在,所以
{
p++;
return p; //OK: 可以做为返回值
}

int main()
{
int i=10;
int& p=i;

printf("%d\n", func(p));
return 0;
}

//3)a.cpp
#include <stdio.h>

int& func()
{
static int i=10; //因为是全局的静态区
int& p=i;
p++;
return p; //OK:可以做为返回值
}

int main()
{
printf("%d\n", func());
return 0;
}

//4) a.cpp
#include <stdio.h>

int& func()
{
static int i=10; //因为是全局的静态区
i++;
return i; //OK:可以做为返回值
}

int main()
{
printf("%d\n", func());
return 0;
}

//Error
而int& func() //因为i是func函数内局部值,所以当func结束时不再存在,所以
{
int i=10;
return i; //Error: 可以不能做为返回值
}
原文:http://blog.sina.com.cn/s/blog_4aec321401000dk6.html


//-----------------------值 or 指针-----------------
int testA (void)
{
int b = 1 ;
return b ;
}
char * testB (void)
{
char str[] = "abc" ;
return str ;
}
int main()
{
printf( " the value of testA is %d n", testA() ) ;
printf( " the value of testB is %c ", *( testB() ) ) ;
}

//对于返回值的情况:
testA与main函数同在栈区,testA结束时C++创建临时变量,然后将返回值复制给该临时变量。
printf( " the value of testA is %d n", testA() ) 时输出的是该临时变量的值,testA中的b已经不存在。

//对于返回指针的情况:
这是最复杂的部分。首先,对于上面的情形:返回一个数组的首地址,由于是返回char *类型,所以C++会首先创建一个char *类型的临时变量,再把该数组的首地址赋给临时变量;函数结束后该数组也就被销毁,这就意味着临时变量指向了一个“未声明的地址”,幸运的情况下,这段内存暂时还没有被其他的数据所覆盖,因此还能输出正确的内容。

在testB里面,如果换成char* str="abc";return str; 由于这时str指向的是全局数据区的一段内存地址,所以函数结束后临时变量也指向该地址,所以编译器不会提出警告。但这样的方法是不推荐的。
  
//返回引用:
这中情况的效率最高,它直接返回一个对象,不产生返回值的副本。但同时也要注意避免返回局部引用的情况。

原文:http://www.examda.com/ncre2/C/fudao/20081212/09181292.html


//-----------------------指针-----------------
//如何用char *作为函数返回值?除了用static还有别的方法吗?
char *test()
{
static char s[10]= "sfwefwfew ";
return s;
}
s在函数结束时被销毁,返回的是无意义的值,必须加static。

//我想请问:
1、除了static,有没有别的方法实现(不传入指针参数,仅就return而言),C/C++的那些库函数也是用这样的方式实现的吗?
2static char s[10]的生命周期是不是一直到程序结束?大量使用static会不会影响程序的效率?


static char s[10]的生命周期是一直到程序结束。大量使用static会占用太多的内存。


//返回方式
1、堆上分配内存 (new
char *s = new char[10];
...
return s;
2、静态变量(static
3、常量字符串
char *test()
{
char *s = "ssssss "; //定义常量字符串,在进程退出时释放资源
return s;
}
4、外部定义变量
//C/C++的库都是在函数外分配内存,如
char *ss(const char *src)
{
...
return src;
}
char szSrc[100];
char *p = ss(szSrc);

原文:http://topic.csdn.net/t/20050121/13/3743688.html

7.运算符重载
//定义格式
返回类型 operator运算符(形式参数表) { 函数体 }
参数个数的限定
非成员函数:
  单目运算符:参数表中只有一个参数;
  双目运算符:参数表中只有两个参数
成员函数:
  单目运算符:参数表中没有参数;
  双目运算符:参数表中只有一个参数

//不能重载的运算符
1、不能重载的运算符有: ::, ., .*, ?:
2、必须重载为成员函数的运算符: [], (), –>, =
3、在类成员函数中重载运算符是不允许返回引用的,会出现“返回局部变量的地址”警告
4、cout << f1 << f2;
//用重载运算符表示,只能通过友员来实现
//如果要用成员函数,则会有cout.operator<<(const F& f),所以这是不
// 可能的.因此只能用友员来实现,operator<<(cout,f)
// 而cout是ostream型的,因此有以下标准格式.注意不能加const,因为
//cout是要改变的,会改变里的缓冲成员.
ostream& operator<<( /* 不能加const */ ostream& cout, constF&) //输出运算符的标准重载格式.
friend istream& operator>>(istream& is, F& f){ }//输入运算符重载标准格式



//重载运算符完整例子
//双目运算符重载
#include <iostream>
using namespace std;
class F{
int n;
int d;
public :
F(int n=0, int d=1):n(n),d(d){}
friend ostream& operator<<(ostream& os, const F& f){
os << '[' << f.n << '/' << f.d <<']';
return os;
}
F operator*(const F& o) {
return F(n*o.n,d*o.d);
}
friend F operator/(const F& f1,const F& f2){
return F(f1.n*f2.d,f1.d*f2.n);
}
friend istream& operator>>(istream& is, F& f){
char ch;
is >> f.n >> ch >> f.d;
return is;
}
};

int main()
{
F f1(3,5),f2(11,7),f;
cout << f1 << '*' << f2 << '=' << f1*f2 << endl;
cout << f1 << '/' << f2 << '=' << f1/f2 << endl;
cout << "Input 2 fractions :";
cin >> f1 >> f2;
cout <<"f1=" << f1 << endl;
cout << "f2=" << f2 << endl;
}


* 单目运算符重载
-友元函数形式,返回类型 operatorX(形参)
使用:X obj ---> operatorX(obj);
-成员函数形式 尽量用成员 返回类型 operatorX(/*无形参*/)
使用: X obj ---> obj.operator();
//单目运算符重载
//把后++,后--当作双目运算符,第二个操作数是整形.
#include <iostream>
using namespace std;

class A{
int data;
public :
A(int d=0):data(d){}
friend ostream& operator<<(ostream& os,const A& a){
os << a.data;
return os;
}
friend istream& operator>>(istream& is,A& a){
is >> a.data;
return is;
}
friend A& operator++(A& a){
a.data += 10;
return a;
}
A& operator--(){
data -= 10;
return *this;
}
friend A/* 不能用引用 */ operator++(A& a,int){
A old(a);
a.data += 1;
return old;
}
A /* 不能用引用 */ operator--(int){
A old(*this);
data -= 1;
return old;
}
};

int main()
{
A a1(50),a2(100);
cout << "a1=" <<a1 << endl;
cout << "a2=" <<a2 << endl;
cout << "++a1=" << ++a1 << endl;
cout << "--a1=" << --a1 << endl;
cout << "a1++=" << a1++ << endl;
cout << "a1=" <<a1 << endl;
cout << "a2--=" << a2-- << endl;
cout << "a2=" <<a2 << endl;

}
运算符重载提供了一个自己规定运算符式作方式的方法,至少有一个操
作数是自定义类型的.基本类型我们是规定不了的.
强制类型转换:类型(数据) --> (不必写返回类型,因为始终与后面的类
型是相同的) operator类型(无形参) 只能写成成员函数,不能是友员.

#include<iostream>
using namespacestd;

classA{
intdata;
public:
A(intd=0):data(d){}
operator int(){
returndata;
}
operator bool(){
returndata!=0;
}
operator char(){
return(char)data;
}
};
intmain()
{
A a1(65),a2(200);
cout << "a1="<< (char)a1 << endl;
intd=a2;
if(a2)
cout << "good"<< endl;
}

对自定义类型的对象,使用运算符时,总是调用相应的运算符函数
三目运算符不能重载.
等号是双目运算符也可重载,它也只能是成员.
还有点号('.')不能重载.
双冒号(::)不能重载.
sizeof(类型)没法重载.
#号不是运算符,无所谓重载.
'= () [] -> 类型转换'只能是成员函数来重载,其它的随便,我们建议尽量
使用成员函数来写,有点只能用友元,比如输入输出.
==================================================
+ 双目运算符重载
- 友元形式:返 operator符号(形1,形2)
- 成员形式:返 operator符号(形)
+ 单目运算符重载
- 友元: 返 operator符号(形参)
- 成员: 返 operator符号()
+ 特例
- 加加
- 先加加
- 后加加 返回 operator符号(形,int)
- 减减
- 先减减
- 后减减
原文:http://www.cnblogs.com/wzh206/archive/2010/03/25/1696162.html



posted @ 2011-10-18 20:47  JK00  阅读(367)  评论(0编辑  收藏  举报