Where there is a dream ,there is hope

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  64 Posts :: 0 Stories :: 8 Comments :: 0 Trackbacks

常用链接

留言簿(1)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

 

都是内存对齐,稍有区别,这里测试平台是VS2008,其它平台为测试

#pragma pack( n ),
结构体中的数据成员,除了第一个是始终放在最开始的地方,其它数据成员的地址必须是它本身大小或对齐参数两者中较小的一个的倍数
 即:min(n, sizeof(elem))

结构体整个的大小就没有另外的要求

 

#pragma pack(1)
struct TestA
{
    
char  a;
    
short b;
    
char  c;
}
;

#pragma pack(
4)
struct TestB
{
    
char  a;
    
short b;
    
char  c;
}
;


结果:


在结构体中含有结构体的话,内结构体的大小按照此结构体内最大元素的大小来计算,各个元素的计算方法如下:
min(n, e ||max({e}) )

#pragma pack( 4 )
struct TestA
{
    
char  a;
    
short b;
    
char  c;
}
;

#pragma pack( 
8 )
struct TestB
{
    
char  a;
    
char  c;
    TestA m_a;
}
;

因为TestA最大的元素是short 2B,所以计算TestA的大小的时候,按照2来算



__declspec( align(#) )#pragma pack( n )同时修饰结构体的时候,前者的优先级高,
注意前者与后者的区别:

成员的地址决定于前者及后者,其要么是前者的倍数,要么是后者的倍数,要么是成员的大小的倍数,取最小。

结构体最后的大小于前者有关,其要么是前者的倍数,要么是结构体中最大偏移量的倍数,取最大。

要算出最后结果,必须知道两者的值或缺省值

__declspec(align(1)) struct TestA
{
    
char  a;
    
short b;
    
char  c;
    
double d;
}
;

__declspec(align(
32)) struct TestB
{
    
char  a;
    
short b;
    
char  c;
    
double d;
}
;

输出:

posted @ 2011-03-14 12:21 IT菜鸟 阅读(363) | 评论 (0)编辑 收藏

     摘要: 温故而知新,总结了多种单例模式的写法,考虑上多线程没有非常完美的,各取所需吧 //1.//Easy ,<<Pattern Design>>//Q://When to free the m_singleton space?//when to execute the ...  阅读全文
posted @ 2011-03-09 16:18 IT菜鸟 阅读(309) | 评论 (0)编辑 收藏

总用到这个转化,记下来
wchar_t* atow(char* src)
{
    
int dest_len;
    dest_len 
= MultiByteToWideChar(CP_ACP,0,src,-1,NULL,0);
    wchar_t 
*dest = new wchar_t[dest_len];
    
if(dest == NULL)
        
return NULL;
    MultiByteToWideChar(CP_ACP,
0,src,-1,dest,dest_len);
    
return dest;
}
posted @ 2011-02-28 12:05 IT菜鸟 阅读(371) | 评论 (0)编辑 收藏

static关键字在C和C++中的用法稍有区别,主要是C++扩展了static关键字的作用
C 用法
1.修饰函数成员变量:例

//test.h
void test_static_var();

//test.c
void test_static_var()
{
    
static int var=0;//!不初始化默认为0
    var++;
    printf(
"static int var : %d \n", var);
}


//main.c
int main()
{
    
for (int i=0; i<10; i++)
    
{
        test_static_var();
    }

    getchar();
    
return 0;
}

2.在文件级别(即:在函数体外)修饰 变量或者函数,则表示该变量只在该文件中可见及可用
//test.h
void test_static_funtion_one();
void test_static_fuction_two();

//test.c
void test_static_funtion_one()
{
    printf(
"This is a normal function. \n");
}

static
void test_static_fuction_two()
{
    printf(
"This is a function modified by static. \n");
}


//main.c

int main()
{
    test_static_funtion_one();
    test_static_fuction_two();
    getchar();
    
return 0;
}

这样就会报错:LNK2001: 无法解析的外部符号 "void __cdecl test_static_fuction_two(void)" (?test_static_fuction_two@@YAXXZ)
原因就是test_static_fuction_two()被修饰为static ,如果main中不调用的话,编译可以通过(VS08),否则便以失败
修饰为static后,它只能在test.c中使用。

C++中包含了前两种方法,又扩展了下面两种方法:
3.修饰class 数据成员,称为 静态数据成员
4.修饰class 函数成员,称之为 静态成员函数
//test.h
     class TestA
     
{
     
public:
         
         
static int s_count;
         
static int getCount();
     
public:
         
int m_value;
     }
;

//test.c
    int TestA::s_count=0;
    
int TestA::getCount()
    
{
        
//m_value+=m_value; 静态函数中只能操作静态数据成员
        return s_count;
    }
因为静态成员函数没有传入隐式的this指针,所以,它不能使用. 、->操作符 ;不能是virtual 的,不能和非静态的同名
posted @ 2011-02-16 12:21 IT菜鸟 阅读(196) | 评论 (0)编辑 收藏

当定义一个命名空间时,可以忽略这个命名空间的名称:

     namespce {
         char c;
         int i;
         double d;
     }
     编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条using指令。所以上面的代码在效果上等同于:
     namespace __UNIQUE_NAME_ {
         char c;
         int i;
         double d;
     }
     using namespace __UNIQUE_NAME_;
 
     在匿名命名空间中声明的名称也将被编译器转换,与编译器为这个匿名命名空间生成的唯一内部名称(即这里的__UNIQUE_NAME_)绑定在一起。还有一点很重要,就是这些名称具有internal链接属性,这和声明为static的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用extern声明来进行链接。如果不提倡使用全局static声明一个名称拥有internal链接属性,则匿名命名空间可以作为一种更好的达到相同效果的方法。
注意:命名空间都是具有external 连接属性的,只是匿名的命名空间产生的__UNIQUE_NAME__在别的文件中无法得到,这个唯一的名字是不可见的.
C++ 新的标准中提倡使用匿名命名空间,而不推荐使用static,因为static用在不同的地方,涵义不同,容易造成混淆.另外,static不能修饰class
posted @ 2011-02-16 10:12 IT菜鸟 阅读(652) | 评论 (2)编辑 收藏

原文地址:http://www.cnblogs.com/ly4cn/archive/2005/11/28/286185.html

    指针,在C/C++语言中一直是很受宠的;几乎找不到一个不使用指针的C/C++应用。用于存储数据和程序的地址,这是指针的基本功能。用于指向整型数,用整数指针(int*);指向浮点数用浮点数指针(float*);指向结构,用对应的结构指针(struct xxx *);指向任意地址,用无类型指针(void*)。
    有时候,我们需要一些通用的指针。在C语言当中,(void*) 可以代表一切;但是在C++中,我们还有一些比较特殊的指针,无法用(void*)来表示。事实上,在C++中,想找到一个通用的指针,特别是通用的函数指针简直是一个“不可能任务”。
   
    C++是一种静态类型的语言,类型安全在C++中举足轻重。在C语言中,你可以用void*来指向一切;但在C++中,void*并不能指向一切,就算能,也失去了类型安全的意义了。类型安全往往能帮我们找出程序中潜在的一些BUG。
   
    下面我们来探讨一下,C++中如何存储各种类型数据的指针。

   
    1.  数据指针
     数据指针分为两种:常规数据指针和成员数据指针
     
    1.1 常规数据指针
     这个不用说明了,和C语言一样,定义、赋值是很简单明了的。常见的有:int*, double* 等等。
     如:
     int value = 123;
     
int * pn = &value;
   
     
    1.2 成员数据指针
     有如下的结构:
     struct MyStruct
     {
       
int key;
       
int value;
     };
   
     现在有一个结构对象:
     MyStruct me;
     MyStruct* pMe = &me;
   
     我们需要 value 成员的地址,我们可以:
     int * pValue = &me.value;
     //
     int * pValue = &pMe->value;
   
     当然了,这个指针仍然是属于第一种范筹----常规数据指针。
     
     好了,我们现在需要一种指针,它指向MyStruct中的任一数据成员,那么它应该是这样的子:
     int MyStruct::* pMV = &MyStruct::value;
     
//
     int MyStruct::* pMK = &MyStruct::key;
   
     这种指针的用途是用于取得结构成员在结构内的地址。我们可以通过该指针来访问成员数据:
     int value = pMe->*pMV; // 取得pMe的value成员数据。
     int key = me.*pMK; // 取得me的key成员数据。
   
     那么,在什么场合下会使用到成员数据指针呢?
     确实,成员指针本来就不是一种很常用的指针。不过,在某些时候还是很有用处的。我们先来看看下面的一个函数:
  int sum(MyStruct* objs, int MyStruct::* pm, int count)
  {
      
int result = 0;
      
for(int i = 0; i < count; ++i)
          result 
+= objs[i].*pm;
      
return result;
  }
     
     这个函数的功能是什么,你能看明白吗?它的功能就是,给定count个MyStruct结构的指针,计算出给定成员数据的总和。有点拗口对吧?看看下面的程序,你也许就明白了:
     
     MyStruct me[10=
     {
      {
1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{13,14},{15,16},{17,18},{19,20}
     };
     
     
int sum_value = sum(me, &MyStruct::value, 10);
     
//计算10个MyStruct结构的value成员的总和: sum_value 值 为 110     (2+4+6+8++20)
     
     
int sum_key = sum(me, &MyStruct::key, 10);
     
//计算10个MyStruct结构的key成员的总和:   sum_key 值 为 100       (1+3+5+7++19)
   
     
     也许,你觉得用常规指针也可以做到,而且更易懂。Ok,没问题:
     int sum(MyStruct* objs, int count)
     {
      
int result = 0;
      
for(int i = 0; i < count; ++i)
       result 
+= objs[i].value;
      
return result;
     }

     你是想这么做吗?但这么做,你只能计算value,如果要算key的话,你要多写一个函数。有多少个成员需要计算的话,你就要写多少个函数,多麻烦啊。 
posted @ 2011-02-15 11:19 IT菜鸟 阅读(339) | 评论 (1)编辑 收藏


对于拷贝构造函数引用传递,似乎司空见惯,认为理所当然。但是被问起这个问题,的确是一片茫然,为什么呢?

去网上搜索了一下,的确有很多这方面的知识讲解。

我们先看一下CSDN上的一个帖子的回答:
简单的回答是为了防止递归引用。
具体一些可以这么讲:
 当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导致又一次调用类A的拷贝构造函数,这就是一个无限递归。

这个解释还是蛮具体的。
利用值传递的话,会导致递归引用。

还有一片文章也谈到了这个问题, 我觉得写得也非常好!

为什么拷贝构造函数必须为引用传递,不能是值传递?
链接地址:http://www.cnblogs.com/chio/archive/2007/09/14/893299.html

其中讲到了3个问题
1是拷贝构造函数的作用。
      作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
2是参数传递过程到底发生了什么?
      将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!
i)值传递:
    对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
    对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj);  首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用
ii)引用传递:
    无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).
3是在类中有指针数据成员时,拷贝构造函数的使用?
        如果不显式声明拷贝构造函数的时候,编译器也会生成一个默认的拷贝构造函数,而且在一般的情况下运行的也很好。但是在遇到类有指针数据成员时就出现问题了:因为默认的拷贝构造函数是按成员拷贝构造,这导致了两个不同的指针(如ptr1=ptr2)指向了相同的内存。当一个实例销毁时,调用析构函数free(ptr1)释放了这段内存,那么剩下的一个实例的指针ptr2就无效了,在被销毁的时候free(ptr2)就会出现错误了, 这相当于重复释放一块内存两次。这种情况必须显式声明并实现自己的拷贝构造函数,来为新的实例的指针分配新的内存。

问题1和2回答了为什么拷贝构造函数使用值传递会产生无限递归调用的问题;
问题3回答了回答了在类中有指针数据成员时,拷贝构造函数使用值传递等于白显式定义了拷贝构造函数,因为默认的拷贝构造函数就是这么干的
posted @ 2011-02-14 17:50 IT菜鸟 阅读(761) | 评论 (0)编辑 收藏

(转自:http://grantren.javaeye.com/blog/43289)

一 拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三个问题:

1. 以下函数哪个是拷贝构造函数,为什么?

  1. X::X(const X&);   
  2. X::X(X);   
  3. X::X(X&, int a=1);   
  4. X::X(X&, int a=1, b=2);  

 2. 一个类中可以存在多于一个的拷贝构造函数吗?

3. 写出以下程序段的输出结果, 并说明为什么? 如果你都能回答无误的话,那么你已经对拷贝构造函数有了相当的了解。

  1. #include    
  2. #include    
  3.   
  4. struct X {   
  5.   template<typename T>   
  6.   X( T& ) { std::cout << "This is ctor." << std::endl; }   
  7.   
  8.   template<typename T>   
  9.     X& operator=( T& ) { std::cout << "This is ctor." << std::endl; }   
  10. };   
  11.   
  12. void main() {   
  13.   X a(5);   
  14.   X b(10.5);   
  15.   X c = a;   
  16.   c = b;   
  17. }  

 

解答如下:

1. 对于一个类X,如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数. 

  1. X::X(const X&);  //是拷贝构造函数   
  2. X::X(X&, int=1); //是拷贝构造函数  

 2.类中可以存在超过一个拷贝构造函数, 

  1. class X {      
  2. public:      
  3.   X(const X&);      
  4.   X(X&);            // OK   
  5. };  

注意,如果一个类中只存在一个参数为X&的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化.

  1. class X {   
  2. public:   
  3.   X();   
  4.   X(X&);   
  5. };   
  6.     
  7. const X cx;   
  8. X x = cx;    // error   

如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数.
这个默认的参数可能为X::X(const X&)或X::X(X&),由编译器根据上下文决定选择哪一个.

默认拷贝构造函数的行为如下:
 默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造.
 拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise Copy)的动作.
 a)如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
 b)如果数据成员是一个数组,对数组的每一个执行按位拷贝.
 c)如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值.

 

3.  拷贝构造函数不能由成员函数模版生成. 

  1. struct X {   
  2.     template<typename T>   
  3.     X( const T& );    // NOT copy ctor, T can't be X   
  4.   
  5.     template<typename T>   
  6.     operator=( const T& );  // NOT copy ass't, T can't be X   
  7. };   
  8.   

原因很简单, 成员函数模版并不改变语言的规则,而语言的规则说,如果程序需要一个拷贝构造函数而你没有声明它,那么编译器会为你自动生成一个. 所以成员函数模版并不会阻止编译器生成拷贝构造函数, 赋值运算符重载也遵循同样的规则.(参见Effective C++ 3edition, Item45)


二 针对上面作者的讨论,理解更深了,但是下面我还是会给出一个一般的标准的实现和注意事项:
#include "stdafx.h"
#include 
"stdio.h"
#include 
<iostream>
#include 
<string>


struct Test1 
{
    Test1() 
{ }
    Test1(
int i) { id = i; }
    Test1(
const Test1& test)
    
{
        id 
= test.id;
    }

    Test1
& operator = (const Test1& test)
    
{
        
if(this == &test)
            
return *this;
        id 
= test.id;
        
return *this;
    }

    
int id;
}
;

class Test2
{
public:
    Test2()
{ m_pChar = NULL;}
    Test2(
char *pChar) { m_pChar = pChar;}
    Test2(
int num) 
    

        m_pChar 
= new char[num];
        
for(int i = 0; i< num; ++i)
            m_pChar[i] 
= 'a';
        m_pChar[num
-1= '\0';
    }

    Test2(
const Test2& test)
    
{
        
char *pCharT = m_pChar;

        m_pChar 
= new char[strlen(test.m_pChar)];
        strcpy(m_pChar, test.m_pChar);

        
if(!pCharT)
            delete []pCharT;
    }

    Test2
& operator = (const Test2& test)
    
{
        
if(this == &test)
            
return *this;

        
char *pCharT = m_pChar;
        m_pChar 
= new char[strlen(test.m_pChar)];
        strcpy(m_pChar, test.m_pChar);

        
if(!pCharT)
            delete []pCharT;

        
return *this;
    }

private:
    
char *m_pChar;
}
;

int main(int argc, char* argv[])
{
    
const Test1 ts(1); // Test1()
    const Test1* p_ts = &ts;
    
const Test1 ts2(ts); //Test(const Test1& test)
    const Test1 ts3 = ts; //Test(const Test1& test)
    Test1 ts4; ts4 = ts;  //Test1& operator = (const Test1& test)

    Test2 t(
5);
    Test2 t2(t);
    Test2 t3 
= t2;
    Test2 t4; t4 
= t;
    
return 0;
}

posted @ 2011-02-14 17:38 IT菜鸟 阅读(293) | 评论 (0)编辑 收藏

看到一个简洁的字符串连接函数,颇有启发性
void constr(char* f,char* s)
{
    
while(*f!=0)
    
{
        f
++;
    }

    
while((*f++=*s++)!=0)
    
{

    }

    
}

int main()
{

    
char f[]="Hello";
    
char s[]="World";
    
    constr(f,s);
    printf(
"%s",f);
    getchar();
    
return 0;
}
posted @ 2011-02-14 15:24 IT菜鸟 阅读(577) | 评论 (2)编辑 收藏

VC++的预编译功能
TAG:预编译和宏定义,VC++,VC++的预编译功能
TEXT:
这里介绍VC6的预编译功能的使用,由于预编译详细使用比较的复杂,这里只介绍几个最重要的预编译指令: /Yu, /Yc,/Yx,/Fp。其它的详细资料可以参考: MSDN -> Visual Studio 6.0 Document -> Visual C++ 6.0 Document -> VC++ Programmer Guider - >Compiler and Linker -> Details -> Creating Precompiled Header files
   预编译头的概念:
   所谓的预编译头就是把一个工程中的那一部分代码,预先编译好放在一个文件里(通常是以.pch为扩展名的),这个文件就称为预编译头文件这些预先编译好的代码可以是任何的C/C++代码,甚至是inline的函数,但是必须是稳定的,在工程开发的过程中不会被经常改变。如果这些代码被修改,则需要重新编译生成预编译头文件。注意生成预编译头文件是很耗时间的。同时你得注意预编译头文件通常很大,通常有6- 7M大。注意及时清理那些没有用的预编译头文件。
   也许你会问:现在的编译器都有Time stamp的功能,编译器在编译整个工程的时候,它只会编译那些经过修改的文件,而不会去编译那些从上次编译过,到现在没有被修改过的文件。那么为什么还要预编译头文件呢?答案在这里,我们知道编译器是以文件为单位编译的,一个文件经过修改后,会重新编译整个文件,当然在这个文件里包含的所有头文件中的东西(.eg Macro, Preprocessor )都要重新处理一遍。 VC的预编译头文件保存的正是这部分信息。以避免每次都要重新处理这些头文件。
   根据上文介绍,预编译头文件的作用当然就是提高便宜速度了,有了它你没有必要每次都编译那些不需要经常改变的代码。编译性能当然就提高了。
   要使用预编译头,我们必须指定一个头文件,这个头文件包含我们不会经常改变的代码和其他的头文件,然后我们用这个头文件来生成一个预编译头文件(.pch文件)想必大家都知道 StdAfx.h这个文件。很多人都认为这是VC提供的一个“系统级别”的,编译器带的一个头文件。其实不是的,这个文件可以是任何名字的。我们来考察一个典型的由AppWizard生成的MFC Dialog Based 程序的预编译头文件。(因为AppWizard会为我们指定好如何使用预编译头文件,默认的是StdAfx.h,这是VC起的名字)。我们会发现这个头文件里包含了以下的头文件:
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdisp.h> // MFC Automation classes
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#include <afxcmn.h>
   这些正是使用MFC的必须包含的头文件,当然我们不太可能在我们的工程中修改这些头文件的,所以说他们是稳定的。
   那么我们如何指定它来生成预编译头文件。我们知道一个头文件是不能编译的。所以我们还需要一个cpp文件来生成.pch 文件。这个文件默认的就是StdAfx.cpp。在这个文件里只有一句代码就是:#include“Stdafx.h”。原因是理所当然的,我们仅仅是要它能够编译而已―――也就是说,要的只是它的.cpp的扩展名。我们可以用/Yc编译开关来指定StdAfx.cpp来生成一个.pch文件,通过/Fp编译开关来指定生成的pch文件的名字。打开project - >Setting->C/C++ 对话框。把Category指向Precompiled Header。在左边的树形视图里选择整个工程,Project Options(右下角的那个白的地方)可以看到 /Fp “debug/PCH.pch”,这就是指定生成的.pch文件的名字,默认的通常是 <工程名>.pch。然后,在左边的树形视图里选择StdAfx.cpp,这时原来的Project Option变成了 Source File Option(原来是工程,现在是一个文件,当然变了)。在这里我们可以看到 /Yc开关,/Yc的作用就是指定这个文件来创建一个Pch文件。/Yc后面的文件名是那个包含了稳定代码的头文件,一个工程里只能有一个文件的可以有YC开关。VC就根据这个选项把 StdAfx.cpp编译成一个Obj文件和一个PCH文件。
这样,我们就设置好了预编译头文件。也就是说,我们可以使用预编译头功能了。以下是注意事项:
   1)如果使用了/Yu,就是说使用了预编译,我们在每个.cpp文件的最开头,包含你指定产生pch文件的.h文件(默认是stdafx.h)不然就会有问题。如果你没有包含这个文件,就告诉你Unexpected file end.
   2)如果你把pch文件不小心丢了,根据以上的分析,你只要让编译器生成一个pch文件就可以了。也就是说把 stdafx.cpp(即指定/Yc的那个cpp文件)重新编译一遍就可以了。
posted @ 2011-02-10 10:12 IT菜鸟 阅读(362) | 评论 (0)编辑 收藏

仅列出标题
共7页: 1 2 3 4 5 6 7