优先队列priority_queue 用法详解
内存释放原则
1.谁创建谁释放
2.不改变入口处指针
模板类的使用
http://www.cnblogs.com/assemble8086/archive/2011/10/02/2198308.html
http://m.blog.csdn.net/blog/hackbuteer1/6735704
重载运算符
http://wuyuans.com/2012/09/cpp-operator-overload
能不能同时用static和const修饰类的成员函数
我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量,不是指写在前面表示返回值为常量)。
请问:能不能同时用static和const修饰类的成员函数?
答案是不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。
面向对象三个基本特征
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承,可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。 被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
实现继承是指使用基类的属性和方法而无需额外编码的能力;
接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力
可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
多态,允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
头文件尖括号和双引号的区别
尖括号:在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;
双引号:首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。
面向对象是一种思想,使用c语言来实现下列问题
http://www.cnblogs.com/haippy/archive/2012/12/31/2840501.html
A 如何定义一个类
B如何创建以及销毁对象?
C 如何实现类的继承
定义结构体:
/* 动物类,是所有动物类的基类,也是抽象类 */ struct animal_s_ { char *name; /*< 动物的名称 */ animal_ops_t *animal_ops; /* 动物的基本行为 */ }; /* 动物的基本行为 */ struct animal_ops_s_ { /* 动物吃了什么食物 */ void (*eat)(char *food); /* 动物走了多少步 */ void (*walk)(int steps); /* 动物在说什么 */ void (*talk)(char *msg); };
创建和销毁
/* 基类的构造函数,需要显示调用 */ extern animal_t * animal_init(char *name); /* 基类的析构函数,需要显示调用 */ extern void animal_die(animal_t *animal); animal_t * animal_init(char *name) { assert(name != NULL); size_t name_len = strlen(name); animal_t *animal = (animal_t *)malloc(sizeof(animal_t) + sizeof(animal_ops_t) + name_len + 1); memset(animal, 0, (sizeof(animal_t) + sizeof(animal_ops_t) + name_len + 1)); animal->name = (char *)animal + sizeof(animal_t); memcpy(animal, name, name_len); animal->animal_ops = (animal_ops_t *)((char *)animal + sizeof(animal_t) + name_len + 1); return animal; } /* 基类的析构函数,需要显示调用 */ void animal_die(animal_t *animal) { assert(animal != NULL); free(animal); return; }
实现继承
typedef struct dog_s_ dog_t; struct dog_s_ { animal_t base; /* 继承自 animal 基类 */ /* 以下还可以添加与 dog 相关的属性和方法(函数指针), 如: */ /* char *owner; // dog 的主人 */ /* void (*hunt)(const char *rabbit); // 猎兔犬 */ }; extern dog_t * dog_init(); extern void dog_die(dog_t * dog);
C++编译原理
http://blog.csdn.net/niuox/article/details/8216186
编译过程中,语法分析器的任务是:
A 分析单词是怎样构成的
B 分析单词串是如何构成语言和说明的
C 分析语句和说明是如何构成程序的
D 分析程序的结构
B 以单词为例,语法分析说白了就是将字符串识别成单词
堆、栈、队列内存原理及区别,全局变量,局部变量
http://www.cppblog.com/oosky/archive/2006/01/21/2958.html
http://www.cnblogs.com/TonyEwsn/archive/2010/01/29/1659496.html
程序的局部变量存在于(栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中
队列先进先出,栈先进后出,堆又称为优先队列。
数据结构的栈和堆
首先在数据结构上要知道堆栈,尽管我们这么称呼它,但实际上堆栈是两种数据结构:堆和栈。
堆和栈都是一种数据项按序排列的数据结构。
栈就像装数据的桶或箱子
我们先从大家比较熟悉的栈说起吧,它是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取。这就如同我们要取出放在箱子里面底下的东西(放入的比较早的物体),我们首先要移开压在它上面的物体(放入的比较晚的物体)。
堆像一棵倒过来的树
而堆就不同了,堆是一种经过排序的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。
c中内存怎样分配
以地址的增长方向为上的话,栈地址是向下增长的(向低地址扩展),堆区是向上增长(向高地址扩展)。
1、栈区(stack)— 分配局部变量空间,由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 分配程序员申请的内存空间,一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。如malloc,new等操作
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。而堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。
#include<stdio.h> int a = 0;//全局变量->静态区->全局初始化区 char *p1;//全局变量->静态区->全局未初始化区 int main() { int b;//局部变量->栈 char s[] = "abc";//局部变量->栈 char *p2;//局部变量->栈 char *p3 = "123456";//p3为局部变量->栈,123456\0为字符串常量常量区 static int c =0; //static修饰的局部变量->静态区->全局初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20);//程序员申请的内存空间->堆区 return 0; }
malloc,free,new,delect 区别
http://www.cnblogs.com/hello--the-world/archive/2012/08/15/2639835.html
1.操作对象有所不同。
malloc与free是C++/C语言的标准库函数
new/delete是C++的运算符。执行构造函数和析构函数
它们都可用于申请动态内存和释放内存。
2.用法上也有所不同。
如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。
1)、new自动计算需要分配的空间,而malloc需要手工计算字节数
2)、new是类型安全的,而malloc不是
详细说明:
malloc函数的参数是接受需要分配的内存字节数,如果内存能够满足请求量,那么将会返回:指向被分配的内存块起始位置
free函数释放的是指针指向的内存(不是释放的指针本身,不会删除指针本身),其中指针必须指向所释放内存空间的首地址
new的时候会有两个事件发生:1).内存被分配(通过operator new 函数) 2).为被分配的内存调用一个或多个构造函数构建对象
delete的时候,也有两件事发生:1).为将被释放的内存调用一个或多个析构函数 (会删除对象本身) 2).释放内存(通过operator delete 函数)
简而言之 new/delete能进行对对象进行构造和析构函数的调用进而对内存进行更加详细的工作,而malloc/free不能。
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
1. malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
2.malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
void free( void * memblock );
为什么free 函数不象malloc 函数那样复杂呢?这是因为指针p 的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p 是NULL 指针,那么free
对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。
new/delete 的使用要点
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。
如果用new 创建对象数组,那么只能使用对象的无参数构造函数。例如
Obj *objects = new Obj[100]; // 创建100 个动态对象
不能写成Obj *objects = new Obj[100](1);// 创建100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法 后者相当于delete objects[0],漏掉了另外99 个对象。
sizeof和strlen的区别。
char* ss = "0123456789";
sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是
长整型的,所以是4
sizeof(*ss) 结果 1 ===》*ss是第一个字符 其实就是获得了字符串的第一位'0' 所占的内存空间,是char类
型的,占了 1 位
strlen(ss)= 10 >>>> 如果要获得这个字符串的长度,则一定要使用 strlen
参数必须是字符型指针(char*), 且必须是以'\0'结尾的。当数组名作为参数传入时,实际上数组就退化成指针了。
描述函数调用的整个过程。
1)创建形参变量,为每个形参变量建立相应的存储空间。
2)值传递,即将实参的值复制到对应的形参变量中。
3)执行函数体,执行函数体中的语句。
4)返回(带回函数值、返回调用点、撤消形参变量)。
C++ STL里面的vector的实现机制
vector是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
- 当调用push_back成员函数时,怎么实现?
- 内存足则直接 placement new构造对象,否则扩充内存,转移对象,新对象placement new上去。
- 当调用clear成员函数时,做什么操作,如果要释放内存该怎么做。
- 调用析构函数,内存不释放。 clear没有释放内存,只是将数组中的元素置为空了,释放内存需要delete。
指针计算,指针比较,指针类型装换
指针数组和数组指针的区别。
这两个名字不同当然所代表的意思也就不同。我刚开始看到这就吓到了,主要是中文太博大精深了,整这样的简称太专业了,把人都绕晕了。从英文解释或中文全称看就比较容易理解。
指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针
数组指针:a pointer to an array,即指向数组的指针
还要注意的是他们用法的区别,下面举例说明。
int* a[4] 指针数组
表示:数组a中的元素都为int型指针
元素表示:*a[i] *(a[i])是一样的,因为[]优先级高于*
int (*a)[4] 数组指针
表示:指向数组a的指针
元素表示:(*a)[i]
注意:在实际应用中,对于指针数组,我们经常这样使用:
|
1 2 |
typedef int* pInt; pInt a[4]; |
这跟上面指针数组定义所表达的意思是一样的,只不过采取了类型变换。
代码演示如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream>
using namespace std;
int main() { int c[4]={1,2,3,4}; int *a[4]; //指针数组 int (*b)[4]; //数组指针 b=&c; //将数组c中元素赋给数组a for(int i=0;i<4;i++) { a[i]=&c[i]; } //输出看下结果 cout<<*a[1]<<endl; //输出2就对 cout<<(*b)[2]<<endl; //输出3就对 return 0; } |
注意:定义了数组指针,该指针指向这个数组的首地址,必须给指针指定一个地址,容易犯的错得就是,不给b地址,直接用(*b)[i]=c[i]给数组b中元素赋值,这时数组指针不知道指向哪里,调试时可能没错,但运行时肯定出现问题,使用指针时要注意这个问题。但为什么a就不用给他地址呢,a的元素是指针,实际上for循环内已经给数组a中元素指定地址了。但若在for循环内写*a[i]=c[i],这同样会出问题。总之一句话,定义了指针一定要知道指针指向哪里,不然要悲剧。
类似的还有指针函数和函数指针。
static,const关键字
http://www.cnblogs.com/hellocby/p/3543989.html
http://blog.csdn.net/Eric_Jo/article/details/4138548
c语言中static关键字的作用?
static作用:“改变生命周期” 或者 “改变作用域”
用static声明局部变量,使其变为静态存储方式(静态数据区),作用域不变;用static声明外部变量(全局变量),其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。
c语言中const的作用:节省空间,避免不必要的内存分配,类型检查,防止变量被修改
用const修饰变量,变量 readonly,值不可变,加extend前缀可变为全局,静态存储
const修饰指针,
(1)指针本身是常量不可变
char * const pContent;
const (char*) pContent;
(2)指针所指向的内容是常量不可变
const char *pContent;
char const *pContent;
(3)两者都不可变
const char* const pContent;
用const修饰函数:
(1)void f(const int i) { .........}
对传入的参数进行类型检查,不匹配进行提示
.....
C++中虚拟函数的实现机制。
1、c++实现多态的方法
其实很多人都知道,虚函数在c++中的实现机制就是用虚表和虚指针,但是具体是怎样的呢?从more effecive c++其中一篇文章里面可以知道:是每个类用了一个虚表,每个类的对象用了一个虚指针。具体的用法如下:
class A
{
public:
virtual void f();
virtual void g();
private:
int a
};
class B : public A
{
public:
void g();
private:
int b;
};
//A,B的实现省略
因为A有virtual void f(),和g(),所以编译器为A类准备了一个虚表vtableA,内容如下:
|
A::f 的地址 |
|
A::g 的地址 |
B因为继承了A,所以编译器也为B准备了一个虚表vtableB,内容如下:
|
A::f 的地址 |
|
B::g 的地址 |
注意:因为B::g是重写了的,所以B的虚表的g放的是B::g的入口地址,但是f是从上面的A继承下来的,所以f的地址是A::f的入口地址。
然后某处有语句 B bB;的时候,编译器分配空间时,除了A的int a,B的成员int b;以外,还分配了一个虚指针vptr,指向B的虚表vtableB,bB的布局如下:
|
vptr : 指向B的虚表vtableB |
|
int a: 继承A的成员 |
|
int b: B成员 |
当如下语句的时候:
A *pa = &bB;
pa的结构就是A的布局(就是说用pa只能访问的到bB对象的前两项,访问不到第三项int b)
那么pa->g()中,编译器知道的是,g是一个声明为virtual的成员函数,而且其入口地址放在表格(无论是vtalbeA表还是vtalbeB表)的第2项,那么编译器编译这条语句的时候就如是转换:call *(pa->vptr)[1](C语言的数组索引从0开始哈~)。
这一项放的是B::g()的入口地址,则就实现了多态。(注意bB的vptr指向的是B的虚表vtableB)
另外要注意的是,如上的实现并不是唯一的,C++标准只要求用这种机制实现多态,至于虚指针vptr到底放在一个对象布局的哪里,标准没有要求,每个编译器自己决定。我以上的结果是根据g++ 4.3.4经过反汇编分析出来的。
2、两种多态实现机制及其优缺点
除了c++的这种多态的实现机制之外,还有另外一种实现机制,也是查表,不过是按名称查表,是smalltalk等语言的实现机制。这两种方法的优缺点如下:
(1)、按照绝对位置查表,这种方法由于编译阶段已经做好了索引和表项(如上面的call *(pa->vptr[1]) ),所以运行速度比较快;缺点是:当A的virtual成员比较多(比如1000个),而B重写的成员比较少(比如2个),这种时候,B的vtableB的剩下的998个表项都是放A中的virtual成员函数的指针,如果这个派生体系比较大的时候,就浪费了很多的空间。
比如:GUI库,以MFC库为例,MFC有很多类,都是一个继承体系;而且很多时候每个类只是1,2个成员函数需要在派生类重写,如果用C++的虚函数机制,每个类有一个虚表,每个表里面有大量的重复,就会造成空间利用率不高。于是MFC的消息映射机制不用虚函数,而用第二种方法来实现多态,那就是:
(2)、按照函数名称查表,这种方案可以避免如上的问题;但是由于要比较名称,有时候要遍历所有的继承结构,时间效率性能不是很高。(关于MFC的消息映射的实现,看下一篇文章)
3、总结:
如果继承体系的基类的virtual成员不多,而且在派生类要重写的部分占了其中的大多数时候,用C++的虚函数机制是比较好的;
但是如果继承体系的基类的virtual成员很多,或者是继承体系比较庞大的时候,而且派生类中需要重写的部分比较少,那就用名称查找表,这样效率会高一些,很多的GUI库都是这样的,比如MFC,QT
阐述动态链接库与静态链接库的区别。
解答:静态链接库是.lib格式的文件,一般在工程的设置界面加入工程中,程序编译时会把lib文件的代码加入你的程序中因此会增加代码大小,你的程序一运行lib代码强制被装入你程序的运行空间,不能手动移除lib代码。
动态链接库是程序运行时动态装入内存的模块,格式*.dll,在程序运行时可以随意加载和移除,节省内存空间。
在大型的软件项目中一般要实现很多功能,如果把所有单独的功能写成一个个lib文件的话,程序运行的时候要占用很大的内存空间,导致运行缓慢;但是如果将功能写成dll文件,就可以在用到该功能的时候调用功能对应的dll文件,不用这个功能时将dll文件移除内存,这样可以节省内存空间。
枚举量与宏
按位运算
浙公网安备 33010602011771号