posts - 15,comments - 21,trackbacks - 0
很久没有碰C++,下个项目要开始使用C++,所以很多东西需要捡起来重新看看。从今天起记录一些笔记,方便自己今后查阅。言归正传,先从构造函数的初始化列表说起。我把这个知识点细化成3个问题,1.为什么要有初始化列表,它和构造函数中初始化有什么不一样。2.初始化的顺序。3.一些注意的细节。
      先说第一个问题。我们有两个东西,是必须在初始化列表里完成的,一个是const修饰的变量,一个是引用。这点我就不细说了,查阅资料的都可以找到。下面我具体说说类成员。
class Test1
{
public:
Test1()
{
cout << "Test1 default constructor" << endl;
}
Test1(int i)
{
cout << "Test1 value constructor" << endl;
}
Test1(const Test1 &obj)
{
cout << "Test1 copy constructor" << endl;
}
Test1& operator = (const Test1 &obj)
{
cout << "Test1 = constructor" << endl;
return *this;
}
~Test1()
{
cout << "Test1 destructor" << endl;
}
};
 我定义一个类,然后再定义一个类,其中包含Test1
class Test2
{
public:
    Test2()
    {
        t1 = Test1(1);
    }

private:
    Test1 t1;
}; 
我们在构造函数中初始化Test1,我们看看运行结果
Test1 default constructor
Test1 value constructor
Test1 = constructor
Test1 destructor
Test1 destructor
我们分析下这个输出。Test1 default constructor,这说明在进入Test1构造函数之前,已经初始化了t1成员,并且调用的是无参构造函数。Test1 value constructor这个是Test1(1)创建出来的对象。Test1 = constructor,这个表示条用了拷贝构造函数,Test1 destructor这个表示Test1(1)这个临时对象的析构,Test1 destructor这个表示是t1这个成员对象的析构。从上面的结果来看,构造函数中t1 = Test1(1);其实并不是真正意义是上的初始化,而是一次拷贝赋值。当进入构造函数内部之前,类成员会被默认构造函数初始化。如果说Test1是个很大的对象,这块会造成性能上的开销。所以,这点也是使用初始化列表的原因之一。
      第二我们再来说下顺序问题。简单的原则是初始化列表里的会先于构造函数中,初始化列表里会按照变量声明的顺序。我们具体看看下面的例子。
class Test3()
{
public:
    Test3(
int x,int y,int z):_z(z),_y(y)
    {
        _x 
= x;
    }

private:
    
int _x,_y,_z;
};
按照上面的说法,赋值的顺序是_y,_z,_x。
      第三个是注意问题,每个成员只能在初始化列表里出现一次。
class Test3
{
public:
    Test3(
int x,int y,int z):_z(z),_y(y),_z(x)
    {
        _x 
= x;
    }

private:
    
int _x,_y,_z;
};
比如这种就是问题的。_z被初始化了2次。
posted @ 2012-08-14 10:24 梨树阳光 阅读(1435) | 评论 (3)编辑 收藏
      写服务器的,通常会涉及到内存池的东西,自己在这方面也看了写了一些东西,有些体会,写出来跟大家分享下。
      内存池基本包含以下几个东西,第一,初始化。第二,分配内存。第三,回收内存。所谓初始化,就是在服务器启动的时候,或者第一次需要内存的时候,系统分配很大的一块内存,方便之后的使用。分配内存,就是从内存池中取出需要的内存给外部使用,当然这里需要考虑的是当内存池中没有内存可分配时候的处理。回收内存,简单来说,就是外面对象生命期结束了,将分配出去的内存回收入内存池中。好了简单概念就说完了,我们先来看一种最简单的设计方式。
//为了方便描述,这里附上几个简单的链表操作宏
#define INSERT_TO_LIST( head, item, prev, next ) \
do{ \
if ( head ) \
(head)->prev = (item); \
(item)->next = (head); \
(head) = (item);          \
}while(0)
#define REMOVE_FROM_LIST(head, item, prev, next) \
do{ \
if ( (head) == (item) ) \
{ \
(head) = (item)->next; \
if ( head ) \
(head)->prev = NULL; \
} \
else \
{ \
if ( (item)->prev ) \
(item)->prev->next = (item)->next;          \
\
if ( (item)->next ) \
(item)->next->prev = (item)->prev;          \
} \
}while(0)
struct student
{
      char name[32];
      byte sex;
      
      struct student *prev,*next;
};

static struct mem_pool
{
      //该指针用来记录空闲节点
      
struct student *free;
      //该变量记录分配结点个数
      size_t alloc_cnt;
}s_mem_pool;

//分配内存“块”的函数
bool mem_pool_resize(size_t size)
{
      //该函数创建size个不连续的对象,把他们通过链表的方式加入到s_mem_pool.free中
      for ( size_t i = 0;i < size;++i )
      {
            
struct student *p = (struct student *)malloc(sizeof(struct student));
            
if ( !p )
               return false;
            
            p->prev = p->next = NULL;
            INSERT_TO_LIST(s_mem_pool.free,p,prev,next);

      }

      s_mem_pool.alloc_cnt += size;
}

#define MEM_INIT_SIZE 512  
#define MEM_INC_SIZE 256
//初始化函数
bool mem_pool_init()
{
if ( !mem_pool_resize(MEM_INIT_SIZE) )
            return false;
return true;
}
struct student *get_data()
{
if ( s_mem_pool.free == NULL )
{
            if ( !mem_pool_resize(MEM_INC_SIZE) )
                  return NULL;
}
struct student *ret = s_mem_pool.free;
REMOVE_FROM_LIST(s_mem_pool.free,ret,prev,next)
return ret;
}
void free_data(struct student *p)
{
if ( !p )
            return;
memset(p,0,sizeof(struct student));
INSERT_TO_LIST(s_mem_pool.free,p,prev,next)
}
好了最简单的内存池的大致框架就是这样。我们先来看下他的过程。首先,在mem_pool_init()函数中,他先分配512个不连续的student对象。每分配出来一个就把它加入到free链表中,初始化完成后内存池大概是这样的

接下来就是从内存池中取出一个对象get_data()。函数先去判断是否有空闲的对象,有则直接分配,否则再向系统获取一"块"大的内存。调用一次后的内存池大概是这样的

释放对象,再把对象加入到Free链表中。
以上就是过程的简单分析,下面我们来看看他的缺点。
第一,内存不是连续的,容易产生碎片
第二,一个类型就得写一个这样的内存池,很麻烦
第三,为了构建这个内存池,每个没对象必须加上一个prev,next指针
好了,我们来优化一下它。我们重新定义下我们的结构体
union student
{
    
int index;
    
struct
    {
        
char name[32];
        
byte sex;
    }s;
};

static struct mem_pool
{
    
//该下标用来记录空闲节点
    int free;
    
//内存池
    union student *mem;
    
//已分配结点个数
    size_t alloc_cnt;
}s_mem_pool;

//分配内存块的函数
bool mem_pool_resize(size_t size)
{
    size_t new_size 
= s_mem_pool.alloc_cnt+size;
    union student 
*tmp = (union student *)realloc(s_mem_pool.mem,new_size*sizeof(union student));
    
if ( !tmp )
        
return false;
        
    memset(tmp
+s_mem_pool.alloc_cnt,0,size*sizeof(union student));
    size_t i 
= s_mem_pool.alloc_cnt;
    
for ( ;i < new_size - 1;++i )
    {
        tmp[i].index 
= i + 1;
    }
    
    tmp[i].index 
= -1;
    s_mem_pool.free 
= s_mem_pool.alloc_cnt;
    s_mem_pool.mem 
= tmp;
    s_mem_pool.alloc_cnt 
= new_size;
    
    
return true;
}

#define MEM_INIT_SIZE    512  
#define MEM_INC_SIZE    256
//初始化函数
bool mem_pool_init()
{
    
if ( !mem_pool_resize(MEM_INIT_SIZE) )
        
return false;
        
    
return true;
}

union student 
*get_data()
{
    
if ( s_mem_pool.free == -1 )
    {
        
if ( !mem_pool_resize(MEM_INC_SIZE) )
            
return NULL;
    }
    
    union student 
*ret = s_mem_pool.mem+s_mem_pool.free;
    s_mem_pool.free 
= ret->index;
    
return ret;
}

void free_data(union student *p)
{
    
if ( !p )
        
return;
    
    p
->index = s_mem_pool.free;
    s_mem_pool.free 
= p - s_mem_pool.mem;
}
我们来看看改进了些什么。第一student改成了联合体,这主要是为了不占用额外的内存,也就是我们上面所说的第三个缺点,第二,我们使用了realloc函数,这样我们可以使我们分配出来的内存是连续的。我们初始化的时候多了一个for循环,这是为了记录空闲对象的下标,当我们取出一个对象时,free可以立刻知道下一个空闲对象的位置,释放的时候,对象先记录free此时的值,接着再把free赋值成该对象在数组的下标,这样就完成了回收工作。
我们继续分析这段代码,问题在realloc函数上,如果我们的s_mem_pool.mem已经很大了,在realloc的时候我们都知道,先要把原来的数据做一次拷贝,所以如果数据量很大的情况下做一次拷贝,是会消耗性能的。那这里有没有好的办法呢,我们进一步优化
思路大概是这样
初始化

再次分配的时候,我们只需要重新分配新的内存单元,而不需要拷贝之前的内存单元。

因此基于此思路,我们修改我们的代码
#include <stdio.h>
#include 
<stdlib.h>

struct student
{
    
int index;

    
char name[32];
    
byte sex;
};

static struct mem_pool
{
    
//该下标用来记录空闲节点
    int free;
    
//内存池
    struct student **mem;
    
//已分配块个数
    size_t block_cnt;
}s_mem_pool;

#define BLOCK_SIZE        256        //每块的大小
//分配内存块的函数
bool mem_pool_resize(size_t block_size)
{
    size_t new_cnt 
= s_mem_pool.block_cnt + block_size;
    
struct student **tmp = (struct student **)realloc(s_mem_pool.mem,new_size*sizeof(struct student *));
    
if ( !tmp )
        
return false;
        
    memset(tmp
+s_mem_pool.block_cnt,0,size*sizeof(struct student*));
    
for ( size_t i = s_mem_pool.block_cnt;i < new_cnt;++i )
    {
        tmp[i] 
= (struct student *)calloc(BLOCK_SIZE,sizeof(struct student));
        
if ( !tmp[i] )
            
return false;
            
        size_t j 
= 0;
        
for(;j < BLOCK_SIZE - 1;++j )
        {
            tmp[i][j].index 
= i*BLOCK_SIZE+j+1;
        }
        
        
if ( i != new_cnt-1 )
            tmp[i][j].index 
= (i+1)*BLOCK_SIZE;
        
else
            tmp[i][j].index 
= -1;
    }
    
    s_mem_pool.free 
= s_mem_pool.alloc_cnt*BLOCK_SIZE;
    s_mem_pool.mem 
= tmp;
    s_mem_pool.block_cnt 
= new_cnt;
    
    
return true;
}
 
#define MEM_INC_SIZE    10
//初始化函数
bool mem_pool_init()
{
    
if ( !mem_pool_resize(MEM_INIT_SIZE) )
        
return false;
        
    
return true;
}

struct student *get_data()
{
    
if ( s_mem_pool.free == -1 )
    {
        
if ( !mem_pool_resize(MEM_INC_SIZE) )
            
return NULL;
    }
    
    
struct student *ret = s_mem_pool.mem[s_mem_pool.free/BLOCK_SIZE]+s_mem_pool.free%BLOCK_SIZE;
    
int pos = s_mem_pool.free;
    s_mem_pool.free 
= ret->index;
    ret
->index = pos;
    
return ret;
}

void free_data(struct student *p)
{
    
if ( !p )
        
return;
    
    
int pos = p->index;
    p
->index = s_mem_pool.free;
    s_mem_pool.free 
= pos;
}
这里不一样的地方主要在mem_pool_resize函数中,mem变成了2级指针,每次realloc的时候只需要分配指针数组的大小,无须拷贝对象,这样可以提高效率,但是为了在释放的时候把对象放回该放的位置,我们这里在结构体里加入了index变量,记录它的下标。在内存池里,它表示下个空闲对象的下标,在内存池外,它表示在内存池中的下标。总的来说满足了一个需求,却又带来了新的问题,有没有更好的方法呢,答案是肯定,不过今天先写到这里,明天继续。
posted @ 2012-07-19 11:41 梨树阳光 阅读(3473) | 评论 (2)编辑 收藏
      昨天在看一篇文章的时候,突然想起了这个基础性的问题,自己一直对它的区别不是很清楚,于是今天上午研究下了,分享下自己的理解。(对它很清楚的同学们可以略过此篇文章)
      从存储方式来说,文件在磁盘上的存储方式都是二进制形式,所以,文本文件其实也应该算二进制文件。那么他们的区别呢,各自的优缺点呢?不急,我慢慢道来。
      先从他们的区别来说,虽然都是二进制文件,但是二进制代表的意思不一样。打个比方,一个人,我们可以叫他的大名,可以叫他的小名,但其实都是代表这个人。二进制读写是将内存里面的数据直接读写入文本中,而文本呢,则是将数据先转换成了字符串,再写入到文本中。下面我用个例子来说明。
我们定义了一个结构体,表示一个学生信息,我们打算把学生的信息分别用二进制和文本的方式写入到文件中。
struct Student 
{
    
int num;
    
char name[20];
    
float score;
};
我们定义两个方法,分别表示内存写入和文本写入
//使用二进制写入
void write_to_binary_file()
{
    
struct Student stdu;
    stdu.num 
= 111;
    sprintf_s(stdu.name,
20,"%s","shine");
    stdu.score 
= 80.0f;
    fstream binary_file(
"test1.dat",ios::out|ios::binary|ios::app); //此处省略文件是否打开失败的判断
    binary_file.write((char *)&stdu,sizeof(struct Student));//二进制写入的方式
    binary_file.close();

//文本格式写入
void write_to_text_file()
{
    
struct Student stdu;
    stdu.num 
= 111;
    sprintf_s(stdu.name,
20,"%s","shine");
    stdu.score 
= 80.0f;
    FILE 
*fp = fopen("test2.dat","a+");   //此处省略文件是否打开失败的判断
    fprintf(fp,"%d%s%f",stdu.num,stdu.name,stdu.score); //将数据转换成字符串(字符串的格式可以自己定义)
    fclose(fp);

//MAIN函数调用前面两个方法
int _tmain(int argc, _TCHAR* argv[])
{
    write_to_binary_file();
    write_to_text_file();
    
    
return 0;
}
我们来看下,文件里面的格式 2进制文件

文本文件


2进制文件里面将111编码成6F,1个字节,这刚好是111的16进制表示,而文本文件中则写成31,31,31用了3个字节,表示111。73   68   69   6E   65 表示shine,之后2进制文件里是几个连续的FE,而文本文件中是38   30......文本文件将浮点数80.000000用了38(表示8)   30(表示0)  2E(表示.)   30(表示0)   30(表示0)   30(表示0)   30(表示0)   30(表示0)   30(表示0),二进制文件用了4个字节表示浮点数00   00   A0   42
通过这里我们可以初见端倪了,二进制将数据在内存中的样子原封不动的搬到文件中,文本格式则是将每一个数据转换成字符写入到文件中,他们在大小上,布局上都有着区别。由此可以看出,2进制文件可以从读出来直接用,但是文本文件还多一个“翻译”的过程,因此2进制文件的可移植性好。
posted @ 2012-07-12 09:59 梨树阳光 阅读(12939) | 评论 (5)编辑 收藏

先看一个简单的使用例子

求任意个自然数的平方和:

int SqSum(int n,)   
{   
    va_list arg_ptr;   
    
int sum = 0,_n = n;   
    arg_ptr 
= va_start(arg_ptr,n);   
    
while(_n != 0)   
    {   
        sum 
+= (_n*_n);   
        _n 
= va_arg(arg_ptr,int);   
    }   
    va_end(arg_ptr);   
    
return sum;   
}
首先解释下函数参数入栈情况 在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:
最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
| 最后一个可变参数(高内存地址处) | 第N个可变参数 | 第一个可变参数 | 最后一个固定参数 | 第一个固定参数(低内存地址处)
明白上面那个顺序,就知道其实可变参数就是玩弄参数的地址,已达到“不定”的目的 下面我摘自VC中的源码来解释
va_list,va_start,va_arg,va_end宏
1.其实va_list就是我们平时经常用的char* typedef char * va_list;
2.va_start该宏的目的就是将指针指向最后一个固定参数的后面,即第一个不定参数的起始地址 #define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) ) v即表示最后一个固定参数,&v表示v的地址, #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) 该宏其实是一个内存对齐的操作。即表示大于sizeof(n)且为sizeof(int)倍数的最小整数。这句话有点绕,其实举几个例子就简单了。比如1--4,则返回4,5--8则返回8
3.va_arg 该宏的目的是将ap指针继续后移,读取后面的参数,t表示参数类型。该宏首先将ap指针移动到下一个参数的起始地址ap += _INTSIZEOF(t),然后将本参数的值返回 #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
4.va_end将指针赋空 #define va_end(ap) ap = (va_list)0 有了这个分析我们可以把上例中的代码重新翻译下
int SqSum(int n,)   
{   
    
char *arg_ptr;   
    
int sum = 0,_n = n;   
    arg_ptr 
= (char *)&+ 4;//本机上sizeof(int) = 4   
    while(_n != 0)   
    {   
        sum 
+= (_n*_n);   
        arg_ptr 
+= 4;   
        _n 
= *(int *)(arg_ptr-4);   
    }   
    arg_ptr 
= (void*)0;   
}  
这样我们也可以写出我们自己的printf了
posted @ 2012-07-12 09:51 梨树阳光 阅读(1020) | 评论 (1)编辑 收藏

1.制作自己的动态库和静态库

linux下动态库以.so结尾,静态库以.a结尾,它们都以lib开头,比如一个库名为net,那么它的全名应该是libnet.so或者libnet.a

我们有两个文件,hello.ctest.c,下面是两个文件的内容

//hello.c

#include <stdio.h>

 

void my_lib_func()

{

printf("Library routine called\r\n");

}

 

//test.c

#include <stdio.h>

 

int main()

{

my_lib_func();

 

return 1;

}

 

test.c调用了hello.c的方法,我们把hello.c封装成库文件。无论是静态库还是动态库,都是由.o文件组成,我们先把gcc -c hello.c生成.o文件

制作静态库

ar crv libmyhello.a hello.o,ar是生成静态库的命令,libmyhello.a是我的静态库名。下一步就是在我的程序中使用静态库

可以看到已经有了Library routine called的结果,说明调用成功了。

下面我们删除libmyhello.a,看看程序是否还是运行正常


我们发现程序依然运行正常,说明静态库已经连接进入我们的程序中

制作动态库


我们看见动态库libmyhello.so已经生成,下面继续使用


找不到库文件,这个时候我们把so文件拷贝到/usr/lib下面


运行成功

 

2.动态库和静态库同时存在的调用规则

我们可以发现,不论是动态库还是静态库,程序编译连接的时候都是加的参数-l,那么当他们同时存在的时候,程序会选择动态库还是静态库呢。我们做个尝试。


我们同时存在libmyhello.alibmyhello.so,我们发现运行的时候,出现找不到动态库的错误,由此,我们可以得出结论,同时存在动态库和静态库的时候,gcc会优先选择动态库

posted @ 2012-07-11 15:15 梨树阳光 阅读(1455) | 评论 (0)编辑 收藏
仅列出标题
共2页: 1 2