#define是C语言中提供的宏定义方法,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率
有两种常用的宏定义: (1)简单的宏定义: #define <宏名> <字符串> 例: #define PI 3.1415926 (2)带参数的宏定义 #define <宏名> (<参数表>) <宏体> 例: #define A(x) x
在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输出,它实现以下的功能: (1)文件包含 可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。 (2)条件编译 预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。 (3)宏展开 预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。 (4)防止一个头文件被重复包含
优点:1、方便程序修改; 2提高程序的运行效率、
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码。
malloc需要自己计算字节数,new会根据类型自动计算字节数 malloc返回一个空指针,需要自己进行类型转换,new 自动匹配指针类型 malloc不会自动调用类的构造函数,new会自动调用类的构造函数; free一个对象时,不会自动调用类的构造函数,delete一个对象时会自动调用类的析构函数; malloc,free是函数,new,delete是运算符; new和delete:用于元素的申请 new[]和delete[]用于数组的申请; malloc函数的原型:void *malloc(unsigned int size)
存储方式一样(都是静态存储),但是两者的作用域不同,非静态全局变量的作用域是整个源程序,在各个文件中都有效,静态全局变量只局限于一个源文件中。
把普通局部变量改为静态后改变了生存期,生存期变长。
只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件.
-静态成员变量属于整个类所有 -静态成员变量的生命期不依赖于任何对象,为程序的生命周期 -可以通过类名直接访问公有静态成员变量 -所有对象共享类的静态成员变量 -可以通过对象名访问公有静态成员变量 -静态成员变量需要在类外单独分配空间 -静态成员变量在程序内部位于全局数据区 (Type className::VarName = value)
-静态成员函数是类的一个特殊的成员函数 -静态成员函数属于整个类所有,没有this指针 -静态成员函数只能直接访问静态成员变量和静态成员函数 -可以通过类名直接访问类的公有静态成员函数 -可以通过对象名访问类的公有静态成员函数 -定义静态成员函数,直接使用static关键字修饰即可
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。。多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,就是用基类的引用指向子类的对象,也可以说是同一种事物表现出的多种形态。 实现原理: 多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是面向对象编程的核心思想之一,能够很有效地提高程序的可扩充性。因此我们有必要深入探索一下它的实现原理。理解了原理才能更好的使用。 示例代码:
#include <stdio.h> #include <string> #define trace(fmt, ...) printf("[trace] %s:%s:%d " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) class IClient { public: IClient(){}; virtual ~IClient(){}; virtual ssize_t recv(char *buff, size_t len) = 0; }; class CStreamClient: public IClient { public: CStreamClient(){}; ~CStreamClient(){}; ssize_t recv(char *buff, size_t len) { trace("recv %d bytes in %p\n", len, buff); return len; } }; int main(int argc, char **argv) { CStreamClient streamclient; IClient &client = streamclient; client.recv(NULL, 0); return 0; }拥有虚函数的类都有属于自己的虚表存放在.text段中,实例化后对象拥有一个内建变量_vptr虚表指针,它指向了实际对象的虚表,通过虚表能够得到实际对象的虚函数地址。
memcpy函数的原型为:void *memcpy(void *destin, void *source, unsigned n); 如果 destin 和 source 的区域存在重叠呢?代码如下:
#include <string.h> #include <stdio.h> #include <iostream> int main() { char a[11] = "1234567890"; memcpy(&a[2], a, 5); printf("%s", a); int d; std::cin >> d; return 0; }结果:1212345890
继承:保持已有类的特性来构造新类的过程称为继承 派生:在已有类的基础上新增自己的特性而产生新类的过程称为派生 内部访问:类中定义的方法访问本类中的成员变量称为内部访问,内部访问可以访问公有、私有和保护变量 外部访问:通过类的对象来直接访问对象的成员变量,只能访问公有成员变量,如 tree.leaf 继承方式分为三类,public类型, protected类型,private类型 公有继承(public)时,对基类中的公有成员和保护成员的访问属性保持不变,而对基类的私有成员则不能访问。 保护继承(protected)时,基类的公有成员和保护成员被派生类继承后变成派生类的保护成员,而基类的私有成员在派生类中不能访问。 私有继承(private)时,基类的公有成员和保护成员被派生类继承后变成派生类的私有成员,而基类的私有成员在派生类中不能访问。
不相关是指两个不同的类之间没有关系,继承是指指两个类之间满足继承关系,其中 B 继承自 A 表示 B是一个A,比如女人、男人都是人, 那么女人男人就可以从人那里继承而来。复合关系是指一个类包含一个类,比如一个二维点类(point)含有 x、y 坐标,一个圆类(circle)含有一个圆心和半径,因此Circle 类应该包含 Point 类。他们之间应该是复合关系。
public, protected, private, 默认是 private 类型
如果C++中派生类的成员与基类相同,会在内存中存在一份与基类同名的成员,比如:
class base { int j; public: int i; void func(); }; class derived : public base{ public: int i; void access(); void func(); }; void derived::access() { j = 5; //error i = 5; //引用的是派生类的i base::i = 5; //引用的是基类的i func(); //派生类的 base::func(); //基类的 } derived obj; obj.i = 1; obj.base::i = 1;一般不会定义同名的成员变量,会定义同名的成员函数
在类的定义中,前面有 virtual 关键字的成员函数就是虚函数,virtual 关键字只用在类定义里的函数声明中,写函数体时不用 virtual。构造函数和静态成员函数不能是虚函数。
C++引用的底层也是指针实现的,那C++为什么还需要引入引用呢? 1.引用是为了支持C++运算符重载,比如,使用引用传递参数实现运算符重载,重载后使用运算符时就成了 a = b - c ,这样与运算符的使用比较相近,但是写 a = &b - &c 就显得很奇怪。 2.使用指针经常容易犯错误:1.操作空指针,2.操作野指针,3.不知不觉地改变了指针的值 3.使用引用的特性与好处:1.不存在空引用,2.必须初始化,3.一个引用永远指向他初始化的那个对象
虚析构函数存在的原因:通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数。但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后再调用基类的析构函数。 解决方法:把基类的析构函数声明为 virtual,派生类的析构函数可以不用 virtual 进行声明;通过基类的指针删除派生类对象时,首先调用派生类的析构函数,再调用基类的析构函数。
类模板的用法
类模板名<真实类型参数表> 对象名(构造函数实参表); Pair<string, int> student("Tom", 19); 编译器由类模板生成类的过程叫类模板的实例化,由类模板实例化得到的类,叫模板类。同一个类模板的两个模板类是不兼容的。类模板的“<类型参数表>"中可以出现非类型参数: template <class T, int size> class CArray{ T array[size]; public: void Print() { for(int i = 0; i < size(); i++) cout << array[i] << endl; } };C++中使用了 mangle 技术,对函数重载的函数名加上编译器中自定义规则的表示符,编译后同名函数的名称会不一样,C语言不支持重载,如果在C++中想要调用C语言开发的一些API,就需要使用 extern “C” 修饰函数声明。为了让C语言和C++都能够调用同一个API,可以在 extern “C” 的基础上使用条件编译。代码如下所示:
//sum.h文件声明 #ifndef __SUM_H #define __SUM_H #ifdef __cplusplus extern "C" { #endif int sum(int a, int b); #ifdef __cplusplus } #endif #endif //sum.c文件实现 #include "sum.h" int sum(int a, int b) { return a + b; }常量指针是指向常量的指针,不能通过常量指针更改所指向变量的值;指针常量是指针值不能更改的指针,指针的值不可更改。两种指针的定义方式也不一样。
//指向常量的指针 const int a = 3; const int * pa = &a; int * ptra = &a; //error! //指针常量 int num = 0; int * const ptr = # ptr = ptr; //error! ptr 的值已经不能被修改结果: 可以看出,在参数较多的构造函数中,不能简单的采用参数较少的构造函数来简化流程。 但是目前有两种办法: 1)采用 placement new 技术,new(this) Copy_construction(a, b); this->c = c; 2)类似于派生类构造函数调用基类构造函数:
Copy_construction(a, b, c) : Copy_construction(a, b) { this->c = c; }不过这依赖于编译器的实现。 最稳妥的办法还是采用每个构造函数都写一遍,多点代码量罢了。
