C++学习笔记

1.int main(int argc,char **argv)里的argc和argv各是什么作用和含义

char  *argv[]是一个字符数组,其大小是int  argc,主要用于命令行参数  argv[]  参数,数组里每个元素代表一个参数;
比如你输入   
   test   a.c   b.c   t.c   
   则   
   argc   =   4   
    
   argv[0]   =   "test"   
   argv[1]   =   "a.c"   
   argv[2]   =   "b.c"   
   argv[3]   =   "t.c"
 

2.关于private,protected,public成员

第一:private, public, protected 访问标号的访问范围。

private:只能由1.该类中的函数、2.其友元函数访问。不能被任何其他访问,该类的对象也不能访问。

protected:可以被1.该类中的函数、2.子类的函数、以及3.其友元函数访问。但不能被该类的对象访问。

public:可以被1.该类中的函数、2.子类的函数、3.其友元函数访问,也可以由4.该类的对象访问。

注:友元函数包括3种:设为友元的普通的非成员函数;设为友元的其他类的成员函数;设为友元类中的所有成员函数。

第二:类的继承后方法属性变化。

private 属性不能够被继承。

使用private继承,父类的protected和public属性在子类中变为private;

使用protected继承,父类的protected和public属性在子类中变为protected;

使用public继承,父类中的protected和public属性不发生改变;

 

protected继承和private继承能降低访问权限。

 

3.静态成员static

  静态数据成员有以下特点:对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新; 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。

  函数里的static变量,static类型的变量只能被初始化一次

 

void fun()
{
  static int count = 0;
  count++;
  cout<<count<<endl;
}

如上,每调用fun()一次,count++,如果不加static,则每次进去count都是=0,加了static只有第一次调用fun()时会初始化count,以后都不会。

 

4.c++对象本体与对象实体不一致,应该自定义拷贝构造函数

  对象本体与对象实体一致时,复制构造函数进行的是浅拷贝(默认构造函数会自动完成),即对象a赋值给对象b,将a的所有数据成员值赋给对象b。这里有个问题就是,如果对象a或类中含有指针,那么对象b得到也只是一个指针,而得不到该指针所指向的内容,两个对象的指针指的是同一个地址,其中一个对象修改了指针指向的内容,另一个对象也会受到影响。这里就会出现对象本体(类)与对象实体(类实例)不一致的情况,需要你自定义复制构造函数,将对象a数据成员中指针所指向的内容赋值给对象b中相应的指针数据成员,通常在对象b中要动态分配内存,这时对象a,b中的指针指向的不再是同一地址,修改其中任何一个对象不会影响另外一个对象。可以概括为:对象本体与对象实体一致不一致看是否含有指针,有指针就不一致;无指针就一致。

class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete []pBuffer;}
CExample(const CExample&); //拷贝构造函数
……

 

5.虚函数

指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数

class A
{
public:   void print(){ cout<<”This is A”<<endl;} };
class B:public A
{
public:   void print(){ cout<<”This is B”<<endl;} };
int main()
{ //为了在以后便于区分,我这段main()代码叫做main1   A a;   B b;   a.print();   b.print(); }

  通过class A和class B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is A和This is B

多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。

int main()
{ //main2   A a;   B b;   A* p1=&a;   A* p2=&b;   p1->print();   p2->print(); }

  运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A。问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数

class A
{
public:   virtual void print(){ cout<<”This is A”<<endl;} //现在成了虚函数了 };

class B:public A
{
public:   void print(){ cout<<”This is B”<<endl;} };

现在重新运行main2的代码,这样输出的结果就是This is A和This is B了

 

1)将基类某函数申明为虚函数,说明基类准备让子类在其中修改该函数的实现

2)如果仅是子类中对基类的A()函数重写,则基类对象调用A()时,仍调用基类中A()的实现,而子类对象调用A()时,调用子类中A()的实现

3)如果基类的A()申明为虚函数,则基类对象调用A()时,调用的是子类中A()的实现,即通过基类指针可以访问派生类的虚方法

 

6.常量指针和指针常量有什么区别

int a;

int * const p = &a //指针常量,*p可以修改*p = 8;(OK)

p不可以修改 p++(ERROR)

 

int a,b;

const int *p = &a;//常量指针 *p不可修改 *p = 8;(ERROR)

p 可以修改 p = &b (OK)

 

还有一种const int * const p = &a; // *p 和 p均不可改变了

关键要看const修饰的是谁

int const     const int的写法是一样的

指针的话看const离谁( 是‘*’  还是‘指针变量名’)比较近就是修饰谁的,

比如const *   表示带*运算对象的是常量,也就是*p 不可变 (暗示p可变,p不带‘*’ 嘛)

* const 变量名  表示变量名是常量,也就是p不可变 (暗示*p可变,const没有修饰‘*“ 嘛)

 

7.重载”=”操作符,对象之间可以用”=”赋值

/// 重载"="操作符
    CAppPaymentInReq& operator=(CAppPaymentInReq& other)
    {
        if (this == &other)
        {
            return *this;
        }

        m_headSpDown         = other.m_headSpDownif (other.m_strPaymentcodesms->Exist())
        {
            m_strPaymentcodesms->SetTypeValue(other.m_strPaymentcodesms->GetTypeValue());
        }
        if (other.m_strSignType->Exist())
        {
            m_strSignType->SetTypeValue(other.m_strSignType->GetTypeValue());
        }
        if (other.m_strSignature->Exist())
        {
            m_strSignature->SetTypeValue(other.m_strSignature->GetTypeValue());
        }
        if (other.m_storageIndex.Exist())
        {
            m_storageIndex.SetTypeValue(other.m_storageIndex.GetTypeValue());
        }
        if (other.m_strplatformID.Exist())
        {
            m_strplatformID.SetTypeValue(other.m_strplatformID.GetTypeValue());
        }
        if (other.m_strpassword.Exist())
        {
            m_strpassword.SetTypeValue(other.m_strpassword.GetTypeValue());
        }

        return *this;
    }

 

8.对MAP按照VALUE排序

typedef pair<char, int> PAIR;

int compare(const PAIR a, const PAIR b)
{
    return a.second > b.second;
}

vector<PAIR> vecPair;
for (CH_INT_MAP::iterator it = mapCharNum.begin(); it != mapCharNum.end(); it++)
{
    vecPair.push_back(*it);
}
stable_sort(vecPair.begin(), vecPair.end(), compare);

 

9.__FILE__, __LINE__, __DATE__, __TIME__, __FUNCTION__

 

++有四个常用的预定义名字,分别为:__FILE__,__LINE__,__DATE__,__TIME__

__FILE__:记录文件的路径加名称

__LINE__:记录文件已经被编译的行数

__DATE__:记录文件的编译日期

__TIME__:记录文件的编译时间

__FUNCTION__:记录当前调用的函数名

可以当作变量直接使用,一般用作程序调试

例子:

#include <iostream>

using namespace std;

int main()
{
 
    cout << "File = " << __FILE__ << '\n'
       << "LINE = " << __LINE__ << '\n'
       << "DATE = " << __DATE__ << '\n'
       << "TIME = " << __TIME__
       << endl; 
    getchar();
} 

 

运行结果:

File = G:/program/study/c++/test1.cpp
LINE = 17
DATE = May 27 2004
TIME = 09:59:01

 

10.#和##

#是预处理宏#define使用的,#使#后的第一个参数返回一个带双引号的字符串

示例:

#include <iostream>
using namespace std;

#define toString(s) #s
int main()
{
cout << toString(Hello World!) <<endl;
return 0;
}

 

cout << toString(Hello World!) <<endl;可以看做是cout << "Hello World!" <<endl;

输出Hello World!

 

##连接前后的内容,使之成为一个整体

示例:

#include <iostream>
#include <string>
using namespace std;

#define concatenate(x,y) x##y

int main()
{
string xy ="Hello,World!";
cout << concatenate(x,y) <<endl;
return 0;
}

 

cout << concatenate(x,y) <<endl;等同于cout << xy <<endl;

 

11.gettimeofday()

其中,tv_sec为Epoch到创建struct timeval时的秒数,tv_usec为微秒

struct timeval结构体在time.h中的定义为:

struct timeval
{
    time_t tv_sec;        /* Seconds. */
    suseconds_t tv_usec;    /* Microseconds. */
};

整个timeval表示一个时间点。

int gettimeofday(struct timeval *tv, struct timezone *tz);

gettimeofday 系统调用可以获取系统当前挂钟时间(Wall-Clock Time),整个 struct timeval 值表示的是从 Unix ''epoch''(UTC 时间 1970 年 1 月 1 日)开始到当前流逝的时间

int i;
for (i = 0; i < 4; ++i)
{
gettimeofday(&tv, NULL);
printf("%d\t%d\n", tv.tv_usec, tv.tv_sec);
sleep(1);
}

输出:

442388    1244770435
443119    1244770436
443543    1244770437
444153    1244770438

前面为微秒数,后面为秒数

 

12.snprintf

snprintf函数并不是标准c/c++中规定的函数,但是在许多编译器中,厂商提供了其实现的版本。
在gcc中,该函数名称就snprintf,而在VC中称为_snprintf。 改成_snprintf就可以了

int snprintf(char *str, size_t size, const char *format, ...);

将可变个参数(...)按照format格式化成字符串,然后将其复制到str

(1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0')

(2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0')

函数返回值若成功则返回欲写入的字符串长度,若出错则返回负值

 

13.引用与指针的比较

函数是C++/C程序的基本功能单元,其重要性不言而喻。函数设计的纤细缺点很容易致使该函数被错用,所以光使函数的功能正确是不敷的。本章重点论述函数的接口设计和内部实现的一些规矩。

函数接口的两个要素是参数和返回值。C语言中,函数的参数和返回值的传递方式有两种:值传递(pass by value)和指针传递(pass by pointer)。C++ 语言中多了引用传递(pass by reference)。由于引用传递的性质象指针传递,而使用方式却象值传递,初学者常常迷惑不解,容易引起凌乱。

    引用是C++中的观点,初学者容易把引用和指针混杂一同。一下程序中,n是m的一个引用(reference),m是被引用物(referent)。

    int m;

    int &n = m;

    n相当于m的别名(绰号),对n的任何操纵就是对m的操纵。例如有人名叫王小毛,他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说长道短。所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。

    引用的一些规矩如下:

    (1)引用被创立的同时必须被初始化(指针则可以在任何时候被初始化)。

    (2)不能有NULL引用,引用必须与正当的存储单元关联(指针则可所以NULL)。

    (3)一旦引用被初始化,就不能转变引用的关系(指针则可以随时转变所指的对象)。

    以下示例程序中,k被初始化为i的引用。语句k = j其实不能将k修改成为j的引用,只是把k的值转变成为6。由于k是i的引用,所以i的值也变成了6。

   int i = 5;
    int j = 6;
    int &k = i;
    k = j; // k和i的值都变成了6;

    上面的程序看起来象在玩文字游戏,没有体现出引用的价值。引用的主要功能是传递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。

    以下是“值传递”的示例程序。由于Func1函数体内的x是外部变量n的一份拷贝,转变x的值不会影响n, 所以n的值仍然是0。

   void Func1(int x)
    {
      x = x + 10;
    }
    …
    int n = 0;
    Func1(n);
    cout << “n = ” << n << endl; // n = 0

以下是“指针传递”的示例程序。由于Func2函数体内的x是指向外部变量n的指针,转变该指针的内容将致使n的值转变,所以n的值成为10。

    void Func2(int *x)
    {
        (* x) = (* x) + 10;
    }

    …

    int n = 0;
    Func2(&n);
    cout << “n = ” << n << endl; // n = 10

    以下是“引用传递”的示例程序。由于Func3函数体内的x是外部变量n的引用,x和n是同一个货色,转变x即是转变n,所以n的值成为10。

    void Func3(int &x)
    {
        x = x + 10;
    }

    …

    int n = 0;
    Func3(n);
    cout << “n = ” << n << endl; // n = 10

对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这货色?

    答案是“用恰当的工具做恰到好处的工作”。

    指针能够毫无约束地操纵内存中的如何货色,尽管指针功能强大,但是非常危险。就象一把刀,它可以用来砍树、裁纸、修指甲、剃头等等,谁敢这样用?

    如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不应有的权力。

 

14.单链表倒序

 1 mylist *listReverse(mylist *head)
 2 {
 3     mylist *p=head;
 4     mylist *pfront=NULL,*pNext;
 5     while(p!=NULL)
 6     {
 7         pNext=p->next;
 8         p->next=pfront;
 9         pfront=p;
10         p=pNext;
11     }
12     return pfront;
13 }

 

15.回调函数

  简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,我们就说这是回调函数。

  一个简单的回调函数实现

  下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。 

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc);
void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc);

  ·byte * array:指向元素数组的指针(任意类型)。  这两个函数接受以下参数:

  ·int size:数组中元素的个数。
  ·int elem_size:数组中一个元素的大小,以字节为单位。
  ·CompareFunction cmpFunc:带有上述原型的指向回调函数的指针。

  这两个函数的会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):

  ·-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。
  ·0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。 
  ·1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。

  基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点:

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc)
{
 for(int i=0; i < size; i++)
 {
  for(int j=0; j < size-1; j++)
  {
   //回调比较函数
   if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size))
   {
    //两个相比较的元素相交换
    byte* temp = new byte[elem_size];
    memcpy(temp, array+j*elem_size, elem_size);
    memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size);
    memcpy(array+(j+1)*elem_size, temp, elem_size);
    delete [] temp;
   }
  }
 }
}

  对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串:  注意:因为实现中使用了memcpy(),所以函数在使用的数据类型方面,会有所局限。

 

int __stdcall CompareInts(const byte* velem1, const byte* velem2)
{
 int elem1 = *(int*)velem1;
 int elem2 = *(int*)velem2;

 if(elem1 < elem2)
  return -1;
 if(elem1 > elem2)
  return 1;

 return 0;
}

int __stdcall CompareStrings(const byte* velem1, const byte* velem2)
{
 const char* elem1 = (char*)velem1;
 const char* elem2 = (char*)velem2;
 return strcmp(elem1, elem2);
}

   如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。

 

16.typedef 函数指针

typedef 行为有点像 #define 宏,用其实际类型替代同义字。

 不同点:typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换

typedef int (*MYFUN)(int, int); 

这种用法一般用在给函数定义别名的时候
上面的例子定义MYFUN 是一个函数指针, 函数类型是带两个int 参数, 返回一个int 

简单的函数指针的用法:

//形式1:返回类型(*函数名)(参数表)
char(*pFun)(int);

//typedef char(*pFun)(int)   //跟上一行功能等同
/*typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。*/

char glFun(int a)
{
  return a;
}

void main() {   pFun =glFun;   (*pFun)(2); }

  第一行定义了一个指针变量pFun.它是一个指向某种函数的指针,这种函数参数是一个int类型,返回值是char类型。只有第一句我们还无法使用这个指针,因为我们还未对它进行赋值。

  第二行定义了一个函数glFun().该函数正好是一个以int为参数返回char的函数。我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。

 

posted @ 2014-09-01 14:23  罗维  阅读(199)  评论(0编辑  收藏  举报