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__:记录文件的编译时间
__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的函数。我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。