最近因为要学习NS3,但是NS3又是用C++编写的,里面的模板类,回调还有类的知识基本上都忘了,又找了本书复习下。记一下比较经典的语句和知识,加深自己的理解,同时帮助其他正在学习C++的同学吧。
1. 面向过程与面向对象的区别
C语言是典型的面向过程语言,面向过程的主要特征是,用户可以指明一列可顺序执行的运算,以表示相应的计算过程。面向过程语言在编写的时候是解决问题的角度出发,围绕问题的解决过程分析问题。面向过程分析方法考虑的是问题的具体解决步骤(解决方法)以及解决问题需要的数据(数据的表示),所以在面向过程程序设计中,重点是设计算法(解决问题的方法)和数据结构(数据的表示和存储)。面向过程有明显的开始、明显的中间过程、明显的结束,程序的编制以这个预定好的过程为中心,一旦程序编制好,过程就确定了,程序按顺序执行。
面向对象语言把设计方法从复杂繁琐的编写程序代码的工作中解放出来,符合人的思维方式和现实世界。将所有的事物看成是对象,任何对象都具有某种特征和行为。面向对象程序设计可用于设计和维护越来越庞大和越来越复杂的软件,能满足对软件的可维护、可移植、可扩充、可重用的多项要求。面向对象最核心的四个特征是封装、继承、抽象和多态。
2. 面向对象程序设计的好处:
- 编写程序不再是从计算机的角度考虑问题,而是站在人类思维的角度。
- 程序的可扩展性要比非面向对象的程序设计语言好。
- 能最大限度的保护已有代码。
3. 一些小的知识点
结构体所占用的存储空间
定义两个结构体如下:struct date { int month; int day; int year; }; struct student { int num; char name[10]; char sex; struct date birthday; } int x = sizeof(struct date); int y = sizeof(struct student);
在上面代码中x为12,y为28。student的3个成员:char name[10]为10个字节,int num为4个字节,struct date birthday为12个字节,为什么student的大小变成了28呢?这是字节对齐的问题。
计算机中内存空间都是按字节划分的,似乎对任何类型的变量访问可以从任何地址开始,但实际需要各种类型数据按照一定的规则在空间上排列,而不是按顺序一个一个地排放,这就是对齐问题。各个硬件平台对存储空间的处理有很大不同,大多数平台每次都是从偶地址开始,如果一个int型数据(假设为32位系统)存放在偶地址开始的地方,那么一个读周期就可以读出32b,而如果存放在以奇地址开始的地方,就需要两个读周期,并对两次读出的结果进行拼凑才能得到32b的数据。int i _________________________________ | 0 | 0 | 0 | 0 | | | | | --------------------------------- 0 1 2 3 4 5 6 7 (a) 偶地址开始存放
int i _________________________________ | | 0 | 0 | 0 | 0 | | | | --------------------------------- 0 1 2 3 4 5 6 7 (b) 奇地址开始存放
如图所示,若int型数据从偶地址开始存放,则只需一个读周期就可以读出0~3的4B数据;若int型数据存放在奇地址,则需要两个周期,第一次读出0~3的4个Byte的数据,第二个周期读出4~7的4个Byte的数据,然后再将两个周期读取的数据进行拼接。现代编译器都对该问题进行了优化。
在Windows下,Visual C++规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该类型所占用的字节数的倍数。对齐方式如下:
char: 1的倍数
int:4的倍数
float:4的倍数
double:8的倍数
short:2的倍数
在Windows编译器下,可以通过pragma pack指定对齐的字节数,在Linux下,可以通过_attribute_( (packed, aligned( n ) ) )(n的取值为1-4)设置对齐的字节数枚举的灵活用法,增强传参的安全性,禁止非法数据传入
假设能以3种方式打开一个文件:inpu、output和append。典型的做法是为每一个状态定义一个常数:
const int input = 1; const int output = 2; const int append = 3;
这样打开文件的函数可以写成:
bool open_file(string file_name, int open_mode)
比如:
open_file("abc.txt", append)
这种做法比较简单,无法限制传递的参数,只要是整型的都可以,简单的处理措施就是在函数内部加个判断:bool open_file(string file_name, int open_mode) { if(open_mode<1 || open_mode >3) return false; ...... }
通过定义枚举类型,我们可以省略这个判断:
enum open_modes {input = 1, output, append} bool open_file(string file_name, open_modes om);
typedef与define
简单来说typedef与define的区别就是:typedef是由用户定义一种新的数据类型,而define只是进行简单的字符串替换,下面用一个非常简单的例子说明。
typedef char * ptr_to_char; ptr_to_char pch1, pch2;
这里的pch1和pch2都是指向char类型的指针。
#define ptr_to_char char * ptr_to_char pch1, pch2;
这里的pch1是指向char类型的指针,而pch2只是一个char类型的变量。下面再说一个例子。假设有一个函数原型为:
int mystrcmp(const char *, const char *)
通过typedef定义:
typedef char * PSTR
然后写为:
int mystrcmp(const PSTR, const PSTR)
const PSTR实际上相当于char * const,指向char的常量指针,而不是const char *(指向常量char的指针),原因在于const给予了整个指针本身以常量性。想要通过typdef定义const char *,应该这样:
typedef const char * CPSTR; int mystrcmp(CPSTR, CPSTR);
引用的规则及与指针的区别
- 引用被创建的同时必须被初始化,指针则可以在任何时候被初始化。
- 不能有NULL引用,引用必须与合法的存储单元关联,指针则可以是NULL。
- 一旦引用被初始化,就不能改变引用的关系,指针可以被重新赋值。
参数传递
- 如果是基本数据类型,优先使用值传递
- 如果是自定义数据类型,尤其是传递较大数据时,使用引用传递
- 除非有必要,尽量减少指针的使用
- 无法通过指针传递动态申请内存
现对最后一条补充说明,为什么无法通过指针传递动态申请内存,假设有这样一个函数:
void GetMemory(char *p, int num) { p = (char *)malloc(sizeof(char) * num); }
那么该函数经编译器编译后是这样的:
void GetMemory(char *p, int num) { char * _p; _p = p; _p = (char *)malloc(sizeof(char) * num); }
在进行函数调用时,编译器会为每个参数构造临时副本,指针参数P的副本_P,编译器使_p=p,但是当动态内存申请成功后,_p的内存地址就发生了变化,而p的地址丝毫未变。
#include<iostream> using namespace std; void getMemory(char *p, int num) { char *p2 = NULL; p2 = p; cout<<"p's address: "<<(void *)p<<endl; cout<<"p2's address:"<<(void *)p2<<endl; //p = (char *)malloc(sizeof(char) * num); p = new char[num]; cout<<"p's address: "<<(void *)p<<endl; cout<<"p2's address: "<<(void *)p2<<endl; } int main() { int num = 20; char *ptr = NULL; cout<<"ptr's address :"<<(void *)ptr<<endl; getMemory(ptr, num); cout<<"ptr's address :"<<(void *)ptr<<endl; return 0; } 运行结果: ptr's address :0 p's address: 0 p2's address:0 p's address: 0x1a39030 p2's address: 0 ptr's address :0