文件1:A.h

#include "B.h"

class B;

class A

{

  B   b;

};

 

文件2:B.h

#include "A.h"

class A;

class B

{

  A a;

};
问题:编译无法通过。

原因:执行#include "A.h"时,包含了B.h,包含B.h,又包含了A.h,循环嵌套包含。

解决办法:

  方法1, 将class A 与class B的声明放在同一个文件中,去掉#include "A.h"与#include "B.h".

  方法2,去掉#include "A.h",避免循环嵌套.

 

posted @ 2010-12-19 22:01 Bigcoder 阅读(118) 评论(0) 编辑

  想要做强者,

            首先做事要硬朗,雷厉风行,理清思路,马上动手;

                    然后做人要低调,不与别人发生纷争,内心具有谦让之心,不记恨别人,行事谨慎,一旦有机会马上抓住;

           再者,说话讲艺术,自己说过的话后让别人有话可说,多站在别人的问题思考问题;

                 很重要的一点,经常从管理者与大局的角度思考问题。

posted @ 2010-11-04 19:44 Bigcoder 阅读(56) 评论(1) 编辑

(转载)

windows下创建进程的步骤:
      进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。程序只是一

组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。而进程则不同,它是程序在某个数据集上的执行,是一个动态实体。它因创建

而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上运行的全部动态过程。
  线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在进程中,由进程提供多个线程执行控制。
从内核角度讲线程是活动体对象,而进程只是一组静态的对象集,进程必须至少拥有一个活动线程才能维持运转。

当某个应用程序调用一个创建进程的函数比如CreateProcess或者用户执行某一个程序(其实windows下用户执行一般普通程序是由explorer.exe调用

CreateProcess来完成),操作系统把这个过程分成以下步骤来完成:
1.打开将要在该进程中执行的映像文件。
2.创建Windows执行体进程对象。
3.创建初始线程(栈、堆执行环境初始化及执行线程体对象)。
4.通知Windows子系统新进程创建了(子系统是操作系统的一部分它是一个协助操作系统内核管理用户态/客户方的一个子系统具体的进程为

Csrss.exe)。
5.开始执行初始线程(如果创建时候指定了线程的CREATE_SUSPENDED状态则线程暂时挂起不执行)。
6.在新进程和线程环境中完成地址空间的初始化(比如加载必须的DLL和库),然后开始到进程入口执行。
到这里操作系统完成一个新进程的创建过程。

下面来看下具体每一步操作系统所做的工作:
1.打开将要在该进程中执行的映像文件。
首先操作系统找到执行的Windows映像然后创建一个内存区对象,以便后面将它映射到新的进程地址空间中。

2.创建Windows执行体进程对象。
接下来操作系统调用内部的系统函数NtCreateProcess来创建一个Windwos执行体进程对象。具体步骤是:
(1)建立EPROCESS
*分配并初始化EPROCESS结构块
*从父进程处继承得到进程的亲和性掩码
*分配进程的最大最小工作集尺(由两个参数决定PsMinimumWorkingSet PsMaximumWorkingSet)
*降新进程的配额块设置为父进程配额块地址,并递增父进程配额块的引用计数
*继承Windows的设备名字空间
*将父进程进程ID保存在新进程对象的InheritedFormUniqueProcessId中
*创建该进程的主访问令牌
*初始化进程句柄表
*将新进程的退出状态设置为STATUS_PENDING

(2)创建初始的进程地址空间
*在适当的页表中创建页表项,以映射初始页面
*从MmresidentAvailablePage算出进程工作集大小
*系统空间的非换页部分和系统缓存的页表被映射到进程

(3)初始化内核进程块KPROCESS
(4)结束进程地址空间的创建过程
(5)建立PEB
(6)完成执行体进程对象的创建过程

3.创建初始线程(栈、堆执行环境初始化及执行线程体对象)。
这时候Windows执行体进程对象已经完全建立完成,但它还没有线程所以无法执行,接下来系统调用NtCreateThread来创建一个挂起的新线程它就是

进程的主线程体。

4.通知Windows子系统新进程创建了(子系统是操作系统的一部分它是一个协助操作系统内核管理用户态/客户方的一个子系统具体的进程为

Csrss.exe)。
接下来操作系统通过客户态(Kernel32.dll)给Windows子系统(Csrss)发送一个新进程线程创建的数据消息,让子系统建立自己的进程线程管理块。当

Csrss接收到该消息时候执行下面的处理:
*复制一份该进程和线程句柄
*设置进程优先级
*分配Csrss进程块
*把新进程的异常处理端口绑定到Csrss中,这样当该进程发生异常时,Csrss将会接收到异常消息
*分配和初始化Csrss线程块
*把线程插入到进程的线程列表中
*把进程插入到Csrss的线程列表中
*显示进程启动光标

5.开始执行初始线程(如果创建时候指定了线程的CREATE_SUSPENDED状态则线程暂时挂起不执行)。
到这里进程环境已经建立完毕进程中开始创建的主线程到这里获得执行权开始执行线程

6.在新进程和线程环境中完成地址空间的初始化(比如加载必须的DLL和库),然后开始到进程入口执行。
到这步实质是调用ldrInitializeThunk来初始化加载器,堆管理器NLS表TLS数组以及临界区结构,并且加载任何必须要的DLL并且用

DLL_PROCESS_ATTACH功能代码来调用各DLL入口点,最后当加载器初始化例程返回到用户模式APC分发器时进程映像开始在用户模式下执行,然后它调

用线程启动函数开始执行。

到这里操作系统完成了所有的创建工作,我们写的程序就这样被操作系统调用运行起来了。

 

posted @ 2010-10-22 11:09 Bigcoder 阅读(1015) 评论(0) 编辑

单链表的逆置的实现:

 (1)算法
struct link
{
  int data;
  struct link *next;
};

link reverse(link x)
{
  if( NULL==x )
    return NULL;
 
  link t=NULL;
  link r=NULL, y=x;  //(0)
  while(y!=NULL)
  {
    t = y->next;   //(1)
    y->next = r;   //(2)
    r = y;         //(3) 
    y = t;         //(4)
   }

  return r;     //返回逆置后的链表
}

(二)原理
(0)(1)(2)(3)(4)分别对应以上程序的各序号
第一次while循环
-------------------------------------------
(0) r=NULL, y=x

r=NULL    a    --->    b     --->   c   --->  d --> NULL
          |(0)            
          y            

-------------------------------------------
(1) t =y->next

r=NULL    a    --->    b     --->   c   --->  d --> NULL
          |(0)         | (1)  
          y            t 


--------------------------------------------
(2) y->next = r
              
a    --->  NULL            b     --->   c   --->  d --> NULL
|(0)   (2)  |              | (1)  
y           r              t 
 
---------------------------------------------       
(3) r = y    
          
a    --->  NULL            b     --->   c   --->  d --> NULL
|(0)   (2)                 | (1)  
y                          t 
|(3)
r

--------------------------------------------- 
(4) y = t
              
a    --->  NULL            b     --->   c   --->  d --> NULL
|(0)   (2)                | (1)  
|                         t 
|(3)                      | (4)
r                         y


第二次while循环(并对上面进行整理)
--------------------------------------------- 
(1) t = y->next
              
a    --->  NULL            b     --->   c   --->  d --> NULL
|                          |            |(1)
r                          y            t
                         
--------------------------------------------- 
(2) y->next = r
              
b  --->  a    --->  NULL         c   --->  d --> NULL
|  (2)   |                       |(1)
y        r                       t

--------------------------------------------- 
(3) r = y
              
b  --->  a    --->  NULL         c   --->  d --> NULL
|  (2)                           |(1)
y                                t
|  (3)
r

--------------------------------------------- 
(4) y = t
              
b  --->  a    --->  NULL         c   --->  d --> NULL
|  (2)                           |(1)
|                                t
|  (3)                           |(4)
r                                y


第三个循环 (并对上面的进行整理)
--------------------------------------------- 
(1) t = y->next
              
b  --->  a    --->  NULL         c   --->  d --> NULL
|                                |         |(1)
r                                y         t

以后的与第二次循环同, 最终可得:
              
d ---> c  ---> b  --->  a    --->  NULL 

注:指针r负责指向新的链表的最后一个结点

  指针t负责指向当前链表的第一个结点,通过这两个指针灵活链接构成一个新的链表。

转载自:http://blog.pfan.cn/ljqy/11456.html

posted @ 2010-09-26 20:55 Bigcoder 阅读(527) 评论(0) 编辑

如果函数返回值是一个对象,要考虑 return 语句的效率。例如     
    return String(s1 + s2);
这是临时对象的语法,表示“创建一个临时对象并返回它” 。不要以为它与“先创建一个局部对象 temp 并返回它的结果”是等价的,如
String temp(s1 + s2);
return temp;
实质不然,上述代码将发生三件事。首先,temp 对象被创建,同时完成初始化;然后拷贝构造函数把 temp 拷贝到保存返回值的外部存储单元中;最后,temp 在函数结束时被销毁(调用析构函数) 。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的花费,提高了
效率。
类似地,我们不要将  
return int(x + y); // 创建一个临时变量并返回它
写成
int temp = x + y;
return temp;

由于内部数据类型如 int,float,double 的变量不存在构造函数与析构函数, 虽然该 “临时变量的语法”不会提高多少效率,但是程序更加简洁易读。

 

转载自:http://mx19841031.javaeye.com/blog/211930

posted @ 2010-09-25 22:35 Bigcoder 阅读(281) 评论(0) 编辑

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }

好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error! 注意事项:

(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

(4)流操作符重载返回值申明为“引用”的作用:

流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

例3

#i nclude <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}

(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/bluesky_0205/archive/2008/12/11/3494204.aspx

posted @ 2010-09-25 21:18 Bigcoder 阅读(649) 评论(0) 编辑

看看下面的程序的输出:

#include <stdio.h>
char *returnStr()
{
     char *p="hello world!";
     return p;
}
int main()
{
     char *str;
     str=returnStr();
     printf("%s\n", str);
     return 0;
}

这个没有任何问题,因为"hello world!"是一个字符串常量,存放在静态数据区,把该字符串常量存放的静态数据区的首地址赋值给了指针,所以returnStr函数退出时,该该字符串常量所在内存不会被回收,故能够通过指针顺利无误的访问。

但是,下面的就有问题:

#include <stdio.h>
char *returnStr()
{
     char p[]="hello world!";
     return p;
}
int main()
{
     char *str;
     str=returnStr();
     printf("%s\n", str);
     return 0;
}

"hello world!"是一个字符串常量,存放在静态数据区,没错,但是把一个字符串常量赋值给了一个局部变量(char []型数组),该局部变量存放在栈中,这样就有两块内容一样的内存,这是与前着最本质的区别,当returnStr函数退出时,栈要清空,局部变量的内存也被清空了,所以这时的函数返回的是一个已被释放的内存地址,所以打印出来的是乱码。

如果函数的返回值非要是一个局部变量的地址,那么该局部变量一定要申明为static类型。如下:

#include <stdio.h>
char *returnStr()
{
     static char p[]="hello world!";
     return p;
}
int main()
{
     char *str;
      str=returnStr();
     printf("%s\n", str);

     return 0;
}

这个问题可以通过下面的一个例子来更好的说明:

#include <stdio.h>
//返回的是局部变量的地址,该地址位于动态数据区,栈里
char *s1()
{
     char p[]="Hello world!";
     printf("in s1 p=%p\n", p);
     printf("in s1: string's address: %p\n", &("Hello world!"));
     return p;
}
//返回的是字符串常量的地址,该地址位于静态数据区
char *s2()
{
     char *q="Hello world!";
     printf("in s2 q=%p\n", q);
     printf("in s2: string's address: %p\n", &("Hello world!"));
     return q;
}
//返回的是静态局部变量的地址,该地址位于静态数据区
char *s3()
{
     static char r[]="Hello world!";
     printf("in s3 r=%p\n", r);
     printf("in s3: string's address: %p\n", &("Hello world!"));
     return r;
}
int main()
{
     char *t1, *t2, *t3;
     t1=s1();
     t2=s2();
     t3=s3();
     printf("in main:");
     printf("p=%p, q=%p, r=%p\n", t1, t2, t3);
     printf("%s\n", t1);
     printf("%s\n", t2);
     printf("%s\n", t3);
     return 0;
}

运行输出结果:
in s1 p=0xbff92efb
in s1: string's address: 0x80486ac
in s2 q=0x80486ac
in s2: string's address: 0x80486ac
in s3 r=0x804998c
in s3: string

这个结果正好应证了上面解释,同时,还可是得出一个结论:字符串常量,之所以称之为常量,因为它可一看作是一个没有命名的字符串且为常量,存放在静态数据区。这里说的静态数据区,是相对于堆、栈等动态数据区而言的。静态数据区存放的是全局变量和静态变量,从这一点上来说,字符串常量又可以称之为一个无名的静态变量,因为"Hello world!"这个字符串在函数 s1和s2 中都引用了,但在内存中却只有一份拷贝,这与静态变量性质相当神似。

char *p = "abcdefg";//静态存储区

char p[] = "abcdefg"; // p本身是数组名了,数组里放的字符串,是局部变量,内容是原来的静态区域内容的拷贝!因此返回p实际上返回的局部变量地址而不是静态存储区地址,和上面不同!

转载于:http://hi.baidu.com/happylatch/blog/item/ead1330130a0e2014bfb5126.html

posted @ 2010-09-25 20:48 Bigcoder 阅读(736) 评论(0) 编辑
摘要: 在VC中可以使用GetTickCount()函数获取时间参数,传递到顶点着色器,根据传递时间参数修改顶点坐标值,从而实现动画。阅读全文
posted @ 2010-04-24 20:09 Bigcoder 阅读(114) 评论(0) 编辑
摘要: 之前也看过环境纹理的实现原理,可是理解总不是那么透彻,今天再次看了一遍,以作巩固。  环境纹理的实现方法有多种,其中典型的方法包括:立方体纹理、球型纹理、抛物线纹理。在这三种方法中,立方体纹理的效果最好。下面简单介绍如何使用立方体纹理。首先,准备六张在六个方向上的环境纹理图片,分别是在positive_x,negative_x,positive_y,negative_y,positive_z,ne...阅读全文
posted @ 2010-04-09 23:16 Bigcoder 阅读(410) 评论(0) 编辑
摘要: 题记: 关于指针,推荐看一下csdn飞天御剑流的《再再论指针》,相信对C语言指针会有一个更为清晰全面的理解。 指针是C语言的精华,它是一柄“双刃剑”,用的好与坏就看使用者的功力了。下面就一道面试题,看一下指针与数组的区别。 char *p1, *p2;char ch[12];char **pp;p1 = ch;pp = &ch;p2 = *pp;问p1和p2是...阅读全文
posted @ 2010-03-18 13:40 Bigcoder 阅读(474) 评论(0) 编辑