上一篇说了【C with Class】语法的第一部分,下面继续第二部分:
第二部分:面向过程的编程风格
什么是面向过程我想如果你还不知道,那你绝对不是C程序员!其实我个人感觉面向过程、模块式的C编程风格是最棒的范式,因为平时我们遇到的大多数问题,都可以用这种范式解决,而且设计相当的简洁明快,绝不拖泥带水,相反不停的折腾面向对象模型真的会让你离问题本质越来越远。不管你已经对面向过程范式多么熟悉了,C++环境下究竟还是有些不同的:
1.关于引用:
引用是现代编程语言中常有的一个概念,自然也是C++引入的新语言特性,是C++常用的一个重要内容。然而我不能不吐槽一句,难道就不能用个其他的操作符吗?于习惯使用C进行开发的朋友们,在看到c++中出现的&符号,可能会犯迷糊,因为在C语言中这个符号表示了取地址符,但是在C++中它却用来表示引用,于是乎产生了一大批混乱的写法:
#include <stdio.h>
int main(void)
{
int a = 0; // &a = 0x0012ff60
int *p = &*(int*)0x0012ff60;
printf("The value is: %d\n", *p);
return 0;
}
我真的不想贴出来,每每看到都想吐、、 总之这是一个很奇葩的现象,在一门已经有了指针这个究极武器的语言中再引入引用,真的是很混乱。我的建议是:
a)如果你是C++程序员,用好你的引用吧,别来玩弄指针了;
b)相反你是C程序员,那请自爱,远离引用!!至少C with Class里是禁止使用的。
2.new和delete的使用注意
差不多到目前为止,new和delete操作符是我接受的唯一的C++里面的东西,既然为了面向对象不得不接受他们,那么就一定要用好他们。
new用法:
1. 开辟单变量地址空间
1)int *a = new int; //开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给整型指针a.
2)int *a = new int(5); //作用同上,但是同时将整数赋值为5
2. 开辟数组空间
int *a = new int[100]; //开辟一个大小为100的整型数组空间
delete用法:
1. delete a; //释放单个int的空间
2. delete [] a; //释放int数组空间
一定要注意delete 和delete[] 的区别,C++告诉我们在回收用 new 分配的单个对象的内存空间的时候用 delete,回收用 new[] 分配的一组对象的内存空间的时候用 delete[]。 关于 new[] 和 delete[],其中又分为两种情况:(1) 为基本数据类型分配和回收空间;(2) 为自定义类型分配和回收空间。
请看下面的程序:
#include <iostream>;
using namespace std; class T {
public:
T() { cout << "constructor" << endl; }
~T() { cout << "destructor" << endl; }
}; int main()
{
const int NUM = 3; T* p1 = new T[NUM];
cout << hex << p1 << endl;
// delete[] p1;
delete p1; T* p2 = new T[NUM];
cout << p2 << endl;
delete[] p2;
}
大家可以自己运行这个程序,看一看 delete p1 和 delete[] p1 的不同结果,我就不在这里贴运行结果了。从运行结果中我们可以看出,delete p1 在回收空间的过程中,只有 p1[0] 这个对象调用了析构函数,其它对象如 p1[1]、p1[2] 等都没有调用自身的析构函数,这就是问题的症结所在。如果用 delete[],则在回收空间之前所有对象都会首先调用自己的析构函数。基本类型的对象没有析构函数,所以回收基本类型组成的数组空间用 delete 和 delete[] 都是应该可以的;但是对于类对象数组,只能用 delete[]。对于 new 的单个对象,只能用 delete 不能用 delete[] 回收空间。
所以一个简单的使用原则就是:new 和 delete、new[] 和 delete[] 对应使用。
3.函数参数默认值
没错,很多C程序员早就知道C++可以给函数参数分配默认值,如果调用时不指定,就用默认值。我觉得这个特性真的很不错,可以为设计API提供极大方便,然而C里面是没有的,如果你的强迫症不是特别严重,我觉得可以接受这个特性,如果不幸中枪也不要担心,其实用C我们也可以模拟实现这一特性,就是用最犀利的Macro!!
具体的做法如下:
#include<stdio.h> enum{ plain=,italic=,bold= }; void printString(const char* message, int size, int style) { printf("%s %d %d\n",message,size,style); } #define PRINT_STRING_1_ARGS(message) printString(message, 18, italic) #define PRINT_STRING_2_ARGS(message, size) printString(message, size, italic) #define PRINT_STRING_3_ARGS(message, size, style) printString(message, size, style) #define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4 #define PRINT_STRING_MACRO_CHOOSER(...) \ GET_4TH_ARG(__VA_ARGS__, PRINT_STRING_3_ARGS, \ PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, ) #define PRINT_STRING(...) PRINT_STRING_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) int main(int argc, char * const argv[]) { PRINT_STRING("Hello, World!"); PRINT_STRING("Hello, World!", ); PRINT_STRING("Hello, World!", , bold); return ; }
好吧,确实很复杂,其实可以适当简化的,不过如果你真的是想设计库,上面的完整写法真的是超赞的哦,而且可移植性很高呢,当然这不是我想出来的,原链接在这里:
http://*.com/questions/3046889/optional-parameters-with-c-macros
4.inline函数
以前的C是没有inline这个东西的,对于一些要频繁使用的小函数,我们常常把他们写成宏函数,这样可以降低函数展开的开销。但是C++是有的,于是很多的编译器也为C加上了这个拓展,比如gcc,可是C99出现了,给C加上了这一特性。原本一切都好了,然而杯具的是C99的inline和许多编译器以前加的那个用法是不完全相同的,如果你习惯了gcc风格的inline,请一定要注意啊,我在网络上找到了这个,看看吧:
http://blog.163.com/zhaojie_ding/blog/static/172972895200811545032240
5.重载函数
现实生活中的一个词可能有多种含义,比如,洗衣服、洗头、洗车,都有一个洗字,但是他们的操作方式是不一样的。函数也一样,有时候它们的操作不完全一样但是名字一样,这就是重载函数。
重载函数就是,两个以上的函数取相同的函数名,但是函数形参的个数或者类型不同,编译器会根据实参与形参的类型和个数进行最佳匹配,自动确定调用哪一个函数。为什么要有重载函数呢?因为如果没有重载函数,那么对不同类型的数据进行类似的操作也要定义不同名称的函数,比如加法函数,就必须对整数加法和浮点数加法分别定义不同的函数名:
int nadd(int a, int b);
float fadd(float a, float b);
这样调用需要记住的函数名太多,而且功能类似,很不方便。这是C++的一个语法特性,这一点在创建类时编写构造函数时是很实用的。比如有一个叫三角形的类triangle,你可以用一个至多个参数来创建:
new triangle(x); // 创建了一个等边三角型,边长x
new triangle(x,y); // 创建了一个等腰三角形,底x,腰y
new triangle(x,y,z); // 创建了一个一般三角形
想做到上面的,只要你重载了三个构造函数就可以了。
6.模版函数
有时候我们使用重载函数还不能达到最优的效果,比如,两个求绝对值的函数:
int abs(int x)
{
return x<0 ? -x:x;
}
double abs(double x)
{
return x<0 ? -x:x;
}
大家观察下这两个函数,这两个函数只是返回值类型和参数类型不同,功能完全一样,如果能有办法写一段通用的代码适用于多种不同的数据类型,就是不用像上面那样写两个函数而只是一段代码就能实现两个函数的功能,那代码的复用性不是更高了吗?开发效率也会提高的。这就要函数模板来实现了。函数模板的定义形式是:
template <typename 标识符>
函数定义
没错,模版函数已经设计到模版了,而且最重要的是,这里面已经不是OOP的思想了,而是更高层次的一种范式,即:泛型编程。显然这离C with Class太遥远。