临界区

一个临界区就是一段不会被中断的代码。

使用临界区共有四个函数。为了使用这些函数,你必须定义一个临界区对象,这个对象是一个类型为CRITICAL_SECTION的全部变量。比如,

CRITICAL_SECTION cs;

这个CRITICAL_SECTION数据类型是一个结构,但它的字段只被Windows内部使用。这个变量必须首先被程序中的一个线程通过如下方式初始化:

InitializeCriticalSection(&cs);

这创立了一个叫cs的临界区对象。这个函数的文档包括以下警告:“临界区对象不能被移动或复制。程序不可以修改它,并且必须把它当作不透明的对象。”说白了,这可以被理解为“不要改动或甚至读取它。”

在临界区对象被初始化后,一个线程通过调用以下函数来进入临界区:

EnterCriticalSection(&cs);

这时,我们就说这个线程拥有这个临界区对象。两个线程不能同时拥有同一个临界区。所以,如果一个线程进入了临界区,那么下一个线程在调用EnterCriticalSection来进入同一个临界区对象时会被挂起。这个函数直到第一个线程调用以下函数离开临界区时才会返回:

LeaveCriticalSection(&cs);

在这时,由于调用EnterCriticalSection而被挂起的第二个线程会拥有这个临界区,而且函数调用会返回,使得线程可以继续。

当程序不再需要临界区对象时,可以通过以下函数删除它:

DeleteCriticalSection(&cs);

这会释放为了维护临界区对象而分配的所有系统资源。

UTF8转Unicode:

char* UTF8ToUnicode(char* szUTF8)
{
    int wcscLen = ::MultiByteToWideChar(CP_UTF8, NULL, szUTF8, int(strlen(szUTF8)), NULL, 0);//得到所需空间的大小
    wchar_t* wszcString = new wchar_t[wcscLen + 1];//给'\0'分配空间
    ::MultiByteToWideChar(CP_UTF8, NULL,szUTF8, int(strlen(szUTF8)), wszcString, wcscLen);   //转换
    wszcString[wcscLen] = '\0';
    char *m_char;
    int len = WideCharToMultiByte(CP_ACP, 0, wszcString, int(wcslen(wszcString)), NULL, 0, NULL, NULL);
    m_char = new char[len + 1];
    WideCharToMultiByte(CP_ACP, 0, wszcString, int(wcslen(wszcString)), m_char, len, NULL, NULL);
    m_char[len] = '\0';
    return m_char;
}

 

Unicode转UTF-8:

char* UnicodeToUTF8(wchar_t* wszcString)
{
    int utf8Len = ::WideCharToMultiByte(CP_UTF8, NULL, wszcString, int(wcslen(wszcString)), NULL, 0, NULL, NULL);    //得到所需空间的大小
    char* szUTF8 = new char[utf8Len + 1];    //给'\0'分配空间
    ::WideCharToMultiByte(CP_UTF8, NULL, wszcString, int(wcslen(wszcString)), szUTF8, utf8Len, NULL, NULL);    //转换
    szUTF8[utf8Len] = '\0';
    return szUTF8;
}

 

ANSI转Unicode:

wchar_t* ANSIToUnicode(char *szAnsi)
{
    // ansi to unicode
    //预转换,得到所需空间的大小
    int wcsLen = ::MultiByteToWideChar(CP_ACP, NULL, szAnsi, strlen(szAnsi), NULL, 0);
    //分配空间要给'\0'留个空间,MultiByteToWideChar不会给'\0'空间
    wchar_t* wszString = new wchar_t[wcsLen + 1];
    //转换
    ::MultiByteToWideChar(CP_ACP, NULL, szAnsi, strlen(szAnsi), wszString, wcsLen);
    //最后加上'\0'
    wszString[wcsLen] = '\0';
	return wszString;
}

 

Unicode转Ansi:

char* UnicodeToANSI(wchar_t* wszString)
{
     // unicode to ansi
    //预转换,得到所需空间的大小,这次用的函数和上面名字相反
    int ansiLen = ::WideCharToMultiByte(CP_ACP, NULL, wszString, wcslen(wszString), NULL, 0, NULL, NULL);
    //同上,分配空间要给'\0'留个空间
    char* szAnsi = new char[ansiLen + 1];
    //转换
    //unicode版对应的strlen是wcslen
    ::WideCharToMultiByte(CP_ACP, NULL, wszString, wcslen(wszString), szAnsi, ansiLen, NULL, NULL);
    //最后加上'\0'
    szAnsi[ansiLen] = '\0';
	return szAnsi;
}

 

使用了Win32 API中的GetVolumeInformation函数:MSDN

#include<stdio.h>
#include<iostream>
#include<windows.h>
#include <stdarg.h>//可变参数使用所需头文件
using namespace std;
int main()
{
    LPCSTR path = "D:/";
    char sysNameBuf[MAX_PATH] = {0};
    int status = GetVolumeInformationA(path,
                                       NULL, // 驱动盘名缓冲,这里我们不需要
                                       0,
                                       NULL,
                                       NULL,
                                       NULL,
                                       sysNameBuf, // 驱动盘的系统名( FAT/NTFS)
                                       MAX_PATH);

    if (0!=status)
    {
        printf("盘符:%s\n文件系统名 : %s\n", path,sysNameBuf);
        // 比较字符串
    }
}

其中path为驱动盘的根路径,后面要加一个斜杠。

sysNameBuf接收文件系统名称的缓冲区的指针,例如FAT文件系统或NTFS文件系统。

 

如何获取所有全排列情况?STL中的代码非常精妙,利用next_permutation的返回值,判断是否全排列结束(否则将死循环)。对于给定的一个数组,打印其所有全排列只需如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

void all_permutation(int arr[], int n)//获得C++的全排列
{
    sort(arr,arr+n);    // sort arr[] in ascending order
    do{
        for(int i=0; i<n; printf("%d ",arr[i++]));
        printf("\n");
    }while(next_permutation(arr,arr+n));
}

int main(){
     int a[5]={1,2,3};
      all_permutation(a,3);
}

 

有时候scanf(“%c”,&ch)本应该阻塞等待用户输入一个char型数据的,但为什么会跳过呢?
例:在该程序段中,
int year;
    printf("请输入一个年份:\n");
    scanf("%d",&year);
   // setbuf(stdin,NULL);//或者直接用getchar();
    //在键盘输入一字符,显示其类型(数字、大写字母、小写字母、其他)
    char ch;
    printf("请输入一个字符:\n");
    scanf("%c",&ch);
    if(ch>='1'&&ch<='9')
        printf("this is digital\n");
    else if (ch>='A'&&ch<='B')
        printf("this is capital letter\n");
    else if (ch>='a'&&ch<='z')
        printf("this is letter\n");
    else
        printf("other!\n”);

 

会输出:
请输入一个年份:
2000
请输入一个字符:
other!
 
例2:
以下程序段:
while (n<=0)
{
printf(“请输入字符串长度:\n”);
scanf(“%d”,&n);
}
setbuf(stdin,NULL); //<1>
char a[n],b[n];
printf(“输入字符串:\n”);
for (int i=0; i<n; i++)
{
scanf(“%c”,&a[i]);
}
printf(“输出字符串为:\n”);
for (int i=0; i<n; i++)
{
b[i]=a[n-1-i];
printf(“%c”,b[i]);
}
如果将<1>语句去除,会使结果错误
例:
请输入字符串长度:
5
输入字符串:
hello
输出字符串为:
lleh

纠其根源,我们先来了解一下scanf()是怎么接受数据的。

首先,当我们的pc指向scanf这句时,系统并不是等待用户输入,而是判断输入缓冲区有没有符合格式的内容,如果有,则直接读取。

知道了这个,我就应该明白,scanf(“%c”,&ch);不是没有读到数据,而是读到了我们不知道的数据。

那问题又来了,它读到了什么??

好吧,这就要说到行缓存;

我们用scanf()的时候都要按下enter键,那enter键按了之后去哪儿了??

好吧,问题基本应该知道了,enter键也进入了输入缓存区,也就是scanf(“%c”,&ch);

读到了’\n’;

解决办法,很简单,既然缓存区有东西,那我们就清空它呗~~

setbuf(stdin,NULL);//这个windows和linux下都可以
fflush(stdin);//这个只能windows;

 

 

有没有想过,当我们定义一个对象的时候,该对象是定义在堆上还是栈上的呢?

现在,假设你已经清楚什么是堆,什么是栈。

如果需要在堆上创建对象,要么使用C++的new运算符,要么使用C语言的malloc系列函数。这点没有异议。

那么假如有一个类A,语句如下:

A a;

此时,a是在栈上分配的吗?

其实,这行语句的含义是,使对象a具有“自动存储(automatic storage)”的性质。所谓“自动存储”,意思是这个对象的存储位置取决于其声明所在的上下文。

如果这个语句出现在函数内部,那么它就在栈上创建对象。

如果这个语句不是在函数内部,而是作为一个类的成员变量,则取决于这个类的对象是如何分配的。

一般来说,如果你用new来生成的对象都是放在堆中的,而直接定义的局部变量都是放在栈中的,全局和静态的对象是放在数据段的静态存储区中。

那么,该如何定义一个只能在堆/栈上生成对象的类呢?

只能在堆上

方法:将析构函数设置为私有

原因:C++ 是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。

如下所示:

#include <iostream>
using namespace std;

class A{
  public:
    A(){};
  private:
    ~A(){};//将析构函数设为私有
};

int main(){
    A a;//无法通过编译,提示A类的析构函数是私有的
    A *aa = new A();//通过编译,在堆上生成对象
}

 

只能在栈上

方法:将 new 和 delete 重载为私有

原因:在堆上生成对象,使用 new 关键词操作,其过程分为两阶段:第一阶段,使用 new 在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。将 new 操作设置为私有,那么第一阶段就无法完成,就不能够在堆上生成对象。

#include <iostream>
using namespace std;

class A{
  public:
    A(){};
    ~A(){};
   private:
    void * operator  new ( size_t  t){}//重载new运算符为私有类型
    void operator delete ( void * ptr){}//重载delete运算符为私有类型
};

int main(){
    A a;//通过编译,在栈上建立了对象
    A *aa = new A();//无法通过编译,提示new运算符是A的私有成员
}

 

翻阅以前的代码,时常会看到如下代码:

if(str.size() == 0) {}
if(!vec.size())     {}
if(!list.size())    {}
// ......

这些例子中只是把 size() 的返回值简单地进行布尔逻辑比较,意思是判断容器空与非空。

代码没有逻辑问题,只是可能效率不够高。

在C++的标准库容器中都有一个 empty() 方法,返回布尔值,表明容器当前是否为空。

使用 size() 判断容器非空与否的不好之处在于:某些标准库中的实现中对某些容器的 size() 求值操作不是线性的。

比如 std::list<>,在某些实现中,每一次调用 size() 都会遍历整个 list 来求 size,这会带来一定的性能问题。如果是线性容器则不会有此问题。

总之,如果只是想判断空与非空,则应总是使用 empty(),而不是 size()

运算符重载就是用同一个运算符完成不同的运算功能,和函数重载一样,运算符重载是在编译阶段完成的,体现出静态的多态性。

C++运算符重载的规定如下:

  • 不能改变原运算符的优先级
  • 不能改变原运算符的结合性
  • 默认参数不能和运算符重载一起使用
  • 不能改变原运算符的操作数个数
  • 不能创建新的运算符,且部分已有运算符可能因为存在二义性问题所以无法被重载
  • 当运算符作用于C++内部提供的数据类型时,原来的含义保持不变。
  • 运算符可以被重载用于用户定义的类对象或者用户定义的类对象与内置数据类型变量的组合

C++中不能重载的运算符:

  1. .
  2. .*
  3. ::
  4. ?:
  5. sizeof

运算符重载为类成员函数时主要有3种形式:重载为类的成员函数、重载为类的友元函数、重载为普通函数。

 

重载++,–单目运算符

++和–重载运算符有前缀和后缀两种运算符重载形式,这里以++重载运算符为例,采用成员函数重载。

#include<iostream>
using namespace std;

class A
{
    int n;
public:
    A(int i){n = i;}
    operator ++(){//重载前缀运算符
     n++;
    }
    operator ++(int){//重载后缀运算符
     n = n+2;
    }
void show(){
cout<<"n="<<n<<endl;
}

};

int main()
{
    A a(5);
    A b(5);
    ++a;
    b++;
    a.show();
    b.show();
}

 

输出结果为:

n=6
n=7

 

如果改用成员函数的调用方式,重载后缀运算符的调用方式是a.operator++(1),其中1可以改为任意整数,它只是一个占位符。重载前缀运算符的调用方式是b.operator()。

 

重载下标运算符

下标运算符[]通常用于取数组的某个元素,下标运算符重载可以实现数组下标的越界检测等,如下Words类所示:

class Words
{
    int len;
    char *str;
public:
    Words(char *s){
    str = new char[strlen(s)+1];
    strcpy(str,s);
    len = strlen(s);
    }

    char operator[](int n){
    if(n > len-1){
        cout<<"数组下标越界";
        return ' ';//返回一个特殊字符即可
    }
    else return *(str+n);
    }
};

 

重载运算符new/delete

C++提供了new和delete两个运算符用于内存管理,在大多数情况下它们是非常有效的,但在有些情况下需要自己管理内存,以克服原new与delete的不足,这就是重载运算符new和delete。delete和delete只能被重载为类的成员函数或者普通函数,不能被重载为友元函数,而且不论是否使用关键字static进行修饰,重载了的new和delete均为类的静态成员函数。

重载new运算符的一般格式:

void *类名::operator new(size_t size,其他形参){
     //使用malloc完成分配工作
     //完成自己需要的操作
     return void *型指针;
}

上述new重载函数应返回一个无值型的指针,形参可以有多个,但第1个参数一定是size_t类型的参数,它表示要分配空间的大小,再调用时由系统自动获取。

在使用重载new运算符时先调用new的重载成员函数,再调用该类的构造函数。

重载delete运算符的一般格式:

void *类名::operator delete(void *p){
     //使用free完成内存释放工作
     //完成自己需要的操作
}

在使用重载delete运算符时默认先调用类的析构函数,再调用重载的delete成员函数。

重载new[]/delete[]和重载new/delete类似。

 

重载输入/输出运算符

重载插入运算符的一般格式如下:

friend ostream& operator<< (ostream & stream,类名 &类引用名){
  //函数体
   return stream;
}

 

重载输出运算符的一般格式如下:

friend istream& operator>> (ostream & stream,类名 &类引用名){
  //函数体
   return stream;
}

 

在C++中,对象复制分为浅复制和深复制。

(1)对象的浅复制

当两个对象之间进行复制时,若复制完成后它们还共享某些内存空间,其中一个对象的销毁会影响到另一个对象,这种对象之间的复制称为浅复制。

(2)对象的深复制

当两个对象之间进行复制时,若复制完成后它们不会共享任何内存空间,其中一个对象的销毁不会影响到另一个对象,这种对象之间的复制称为深复制。

通常情况下拷贝构造函数执行的都是浅复制,当有必要时,我们可以自己写一个拷贝构造函数实现深复制。当一个类中的数据成员带有指针时,此时使用默认的拷贝构造函数,类对象的指针数据成员经过浅复制后会指向同一块内存区域,表面上看上去仿佛没事,但一旦销毁其中任意一个对象时,会调用该类的析构函数,那么指针指向的那块内存区域会被回收,此时再访问另一个对象时,这个对象的指针数据成员所指地址的内存区域已经被回收,会产生未定义的结果,要解决这个问题,我们需要重写拷贝构造函数,实现深复制。实现深复制只需要在该类的拷贝构造函数中,动态分配一块大小和被复制的对象的指针指向的内存区域相同大小的内存区域,将数据复制过去,并将指针指向刚刚分配的内存区域即可。

如下为String 类的基本实现,用到了深复制。

class String{
public:
    String(const char *str = NULL);//构造函数
    String(const String &other);//拷贝构造函数
    ~String(void);//析构函数
    String & operator = (const String &other);//重载赋值运算符
private:
    char *m_data;//用于保存字符串
};

String::String(const char *str )//构造函数
{
    if(str == NULL){
        m_data = new char[1];
        *m_data = '\0';
    }
    else{
        int length = strlen(str);
        m_data = new char[length+1];
        strcpy(m_data,str);
    }
}
String::String(const String &other)//拷贝构造函数
{
    int length = strlen(other.m_data);
    m_data = new char[length+1];
    strcpy(m_data,other.m_data);
}
String::~String(void)//析构函数
{
    delete m_data;
}
String &String :: operator = (const String &other)//重载赋值运算符
{
    if(this==&other){//检查自赋值
        return *this;
    }
    delete m_data;
    int length = strlen(other.m_data);
    m_data = new char[length+1];
    strcpy(m_data,other.m_data);
    return *this;
}

 

C++的空类是指设计时不包含任何数据成员和成员函数的类。

实际上,对于一个空类,系统会自动添加以下默认的成员函数:

1.默认构造函数

2.默认拷贝构造函数

3.默认析构函数

4.赋值“=”运算符

5.取地址运算符

6.取地址运算符const

对于一个空类A,sizeof(A)的值为1,这是因为实例化的原因,空类同样可以被实例化,每个实例在内存中都应该有唯一的地址,为了达到这个目的,编译器会给一个空类插入一个字节,这样空类在实例化后在内存中得到唯一的地址,所以空类所占的内存大小是1个字节。