C与C++的编程风格区别

c和c++都是在实践中发展起来的语言。实用性极强。c是与UNIX/Linux的发展相辅相成的。而C++是B.S为了摆脱c与硬件以及底层过于紧密的苦恼,而开发的语言。C++可以说就是c语言的超集。任何c语言的程序理论上都应当是合法的C++程序。

  C语言的特点:过程性编程和结构化编程。函数作为编程的主要载体和任务模块。一方面用for,while,if-else为代表的分支来规划程序结构,另一方面采用自顶向下编程思想。大任务分为小任务,小任务再往下分,表现为,函数套函数。结构清晰明了。

  C++语言特点:1.过程性编程 ----- 函数或者类中的方法设计与开发仍然占据重要地位。

         2.面向对象编程(OOP)------以封装,继承,多态等思想为代表。主要的工作在于类的设计和实例(对象)的应用。

         3.通用编程(generic programming)。------------以模板template为代表。。。实际上到现在自己也没怎么学会这个。要加油了。

一开始,C++的规范并不太一致,C则以K&R C为通用标准。后来ANSI/ISO制定了ANSI/ISO C++标准,同时制订了ANSI/ISO C标准,定义了两种语言都使用的标准C库。。从此,两种语言有了官方的标准并且不断由官方发布更新版本。

在官方标准中,C语言引进了函数原型const类型限定符,并且支持了//注释格式。以及long long等新的整型。

今年寒假,考完研,就找了一本C++和一本C的教材,在毕(shi)业(ye)之前好好温习一下两种语言。两种语言有很多相似之处。下面总结(zhao chao)一下两种语言的相似之处.

第一条:include 的头文件。

  C语言的传统是使用扩展名.h,例如math.h。C++的定位之一是C语言的拓展,所以起初C++也是以.h作为头文件后缀,而且有些C的头文件直接当作C++的头文件。

但是后来C++的标准发生改变:头文件不再使用拓展名,来自C语言的头文件,在头文件名前加上前缀c。例如:math.h --》cmath和iostream.h --》iostream 。同时,这种改变也意味着一些新的特性,比如namespace,等被加入。所以一些老旧的编译器会支持#include<iostream.h>但是不支持#include<iostream>。

头文件命名约定  
头文件类型 约定 范例 说明
C++旧式风格 以.h结尾 iostream.h C++程序可以使用
C旧式风格 以.h结尾 math.h C,C++程序可以使用
C++新式风格 没有拓展名 iostream C++程序可以使用,使用namespace std
转换后的C 加上前缀c,没有拓展名 cmath C++程序可以使用,可以使用不是c的特性

第二条:main函数

  经典C函数头: main() //original C style, omitting int

在C语言中,省略返回类型就是默认函数类型为int。。。但是,C++逐步淘汰这种用法,所以不建议省略 int 返回类型。而且不建议使用 void main()。因为部分系统可能不支持。

此外int main(void)在C和C++中都表示函数不接受任何参数。而int main()在C++中与使用void相同作用,在C中则意味着对是否接受参数保持沉默。(好吧,什么叫保持沉默。。。。)

 

第三条:注释风格

/*-----*/是C的注释风格,//是C++的注释风格。C99之后,C支持了//。两者在两种语言中均可混用。但是应尽量保持风格统一。

同时,应尽量使用C++注释风格,因为它不涉及到结尾符号与起始符号的匹配。有些人写了/* 就容易忘记写 */

第四条:const与 #define 定义符号常量。

编译过程中,先将源程序先交给预处理器(the preprocessor)作相应的处理。然后再编译。不是边编译,边处理。比如#include 将整个头文件替换到#include 这个语句处。#define X Y是先在源程序中查找独立的标记X,将其替换为Y。。。替换完之后再编译。

c中常用#define MAX 100来定义常量。C++支持这种C风格,但是它提供了更适合的const int MAX = 100;

在C中,const定义常量,初始值可以为常量数字或者常量数字参与的表达式,但是不能是const常量或者const常量参与的表达式。数组大小亦如此。

C++中,const常量的初始值和数组大小的初始值均可以为const常量参与的表达式。

#define LIMIT 20
const int LIM = 20;
static int data1[LIMIT];			//C++ Valid, C Valid
static int data2[LIM];			//C++ Valid, C Invalid
const int LIM2 		= 2 * LIMIT;	//C++ Valid, C Valid
const int LIM3		= 2 * LIM; 	//C++ Valid, C Invalid

在头文件中必须使用#define MAX。因为const int MAX = 100.是一种定义。一个变量只能被定义一次。

此外,const常量具有作用域规则,如for循环内部的const常量,在for外失效。但是#define的作用域是从define开始到文件结尾。

使用const要注意以下规则:

  1. 创建常量的通用格式: 注意,声明常量时必须要初始化。而且初始化之后,常量不能进行改变。
    const type name = value;
  2. 在头文件中尽量不要使用const,而应该使用#define。
  3. const可以根据C++的作用域规则将定义限定在特定的函数或者文件中。而#define则在其所在文件范围内均可见
  4. 如果数据类型本身不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针。
  5. 由第四条可知,我们要尽可能使用const,尤其是指针作为函数的参数,而函数又不会通过指针修改指针指向的数据时,这点在类的设计方面更加凸显。

    原因有两条:其一:这样可以避免由于无意间修改数据而导致的编程错误;其二:使用const使得函数能够处理const和非const实参,否则将只能接受非const。所以如果条件允许,则应将指针形参声明为指向const的指针。

第五条:bool类型

此处暂留,等看完if-else之后综合比较。主要观点就是经典C中没有bool类型,C++中有bool。但是自从C99之后,C可以支持bool型了。

C语言的布尔类型

 

第六条:强制转换

强制转换的通用格式:

(typename)value //C-style, both supporting
typename(value) //C++ -style, only C++ supporting

static_cast<typename> (value) //another operator for casting in C++..

第二种C++风格的想法是强制转换类似与函数调用,所以借鉴函数调用格式。value类似与传入参数,typename类似与函数名。但是,一和二一般采用C风格。。第三个是C++独有的强制类型转换操作符。

第七条:指针

1,在C中,将指针视为无符号整数,注意并不是int类型。。具体格式看具体编译器和操作系统的实现。而C++将指针(地址)视为一种独立的类型。所以:

//字符串常量在C和C++中编译时,表示的是字符串所在的内存地址。

char fish[] = "Bubbles"//字符串常量初始化数组。实际上将const char * 赋值给char* 类型的fish。,alright,no pa。
char fish = “Bubbles”;
//这句话在C中能够编译通过,但是会发出警告。将指针赋值给整型变量。默认启用。
//但是在C++中。这句话是错误的。无法编译通错,会报错:不能将const char * 赋值给char类型变量。因为他们是不同的类型。

 

int * a = 100;     //在C语言中将整数赋值给指针,可以编译通过,但是会发出警告。
int * a = 100;     //在C++中,则不可以,因为int 和int *是两种类型。
int * a = (int *)100;//也可以通过强制类型转换。但是,这样做十分危险。。

ps,在C/C++中将NULL初始化指针。要养成这样的好习惯。。不然指针未被赋值时,其内部存储的数据是任意的,无法预测的。十分危险。

2,C,C++对待void类型指针是不同的。在两种语言中,都可以将一个指向任意类型的指针赋值给void *。但是 ,在将一个void *赋值给一个指向具体数据类型的指针时,C++需要一次强制类型转换。C中,这种强制转换是可选的。但是考虑到强制类型转换更能表达指针意义,建议使用强制类型转换。

第八条:string类和C-风格字符串

string类是ISO/ANSI C++ 添加的。使用时要#include。要使用处理C-风格字符串的函数,如strlen,strcmp,要#include 或者#include。。。其余的。。属于OOP和C语言基本功的问题了。。。

另外,C++很早就有istream类,而string类是后期出现的。所以cin>>和cin.getline()都没有考虑针对string类的重载。。所以通过重载>>和getline()为string类和istream类的友元函数实现cin>>str, getline(cin, str)。。不能用cin.getline(string, int)。。

第九条结构体:struct

记住所有C支持的struct的特性,C++都支持。但是C++具有很多独有的特性。而且这种特性是类(class)相似的。

  1. 在C中,声明结构体变量时struct关键字不能省略。但是在C++中可以。C++这种变化是强调结构声明定义了一种新类型。类比类(class),声明类变量时不必加上class关键字。。
  2. C和C++都支持同结构类型的结构变量使用 = 赋值。。。成员值会完全相同。。
  3. 结构体可以声明构造函数,可以声明自己的方法,可以重载操作符,可以有public和private等权限。类的默认权限是private,struct的默认权限是public。稍后总结。。

第十条:逻辑操作符的其它表示方法

C++为逻辑操作符提供标识符表示方法:and,or,not。这些都是C++的保留字,可以直接使用。但是它们在C中不是保留字。只有在程序中#include才能使用它们。
逻辑操作符的另一种表示方式
操作符 替换代表
&& and
|| or
! not

第十一条:函数

   C语言和C++在函数上大体是相同的。C++支持绝大部分C语言的函数语法。而C++增添了很多特有的函数功能,如内联函数,引用变量,默认参数,函数重载和函数模板。函数由函数头和函数体组成。
  1. 函数原型:函数原型就是函数头后加上;。C++中在在第一次使用函数前,必须先用函数原型声明函数,或者直接将函数定义放在前面。否则,编译器会报错,函数未声明.
    在C中对此没有显性要求。可以不声明函数原型。声明函数原型时括号可以为空,这只是表示函数的参数先不告诉编译器,但是参数是确定的。而C++中为空则表示参数为void。同时与fun(...)也不一样。‘...’表示参数不确定。三者要分清楚。当然,从编程风格来讲,C语言中最好也要声明函数原型。
  2. 内联函数与宏定义:内联函数就是编译器在编译时,会直接把函数的指令插入到内联函数调用处。而不是采取堆栈式管理方式,跳转到函数入口处。每一处函数调用都会插入函数指令,但是省去了跳转时间。“空间换时间”。如果函数的执行时间很长,那么跳转时间占比例很小,“不值得”。如果函数的执行时间较短,跳转时间占比例大。但是绝对时间又不大。所以,内联函数适合短小但是经常被调用的函数。因此多应用在类设计中
    同时,编译器也会对内联函数作出判断,如果编译器自己认为函数不宜被设计为内联函数,如函数过大,或者函数含有递归。那么编译器也不会将其编译为内联的。
    内联函数格式:inline 函数原型/函数定义
    C语言中含有参数的宏定义#define FUN(X) XXX 有类似功能。但是两者有本质差别。预处理器#define实现的是编译前的文本替换。将源代码中的文本替换之后,进行编译。而内联函数,是编译时将函数指令替换函数调用。具体差别可以查看C语言关于#define宏定义。
    C语言中自从C99之后,也支持inline函数了。
  3. 引用:引用与const指针相似,必须在声明引用时进行初始化,const变量也是。而const指针和引用都是一旦与某个变量关联,就保持这种联系,不再改变。
    const int * ptr是指向const的指针,int * const ptr是const指针。
    因为引用参数的不确定性,所以不建议对简单的基本类型变量使用引用参数。当参数的数据比较大,如数据结构和类时,使用引用参数将节省空间和时间。但是除非明确要使用引用参数达到修改变量的目的,否则在函数定义时,要使用const Type & argument..
    传递引用的限制更严格,如果实参与引用参数不匹配,如,实参为非左值(字面变量,包含多项的表达式)或者类型不匹配时,当形参引用参数为const时,将生成临时变量,非const时,绝大部分编译器会报错。少数会生成临时变量。所以应尽可能将引用形参声明为const。
  4. 默认参数:一:默认参数可以只在函数原型中声明,二,默认参数必须从右到左添加默认值,默认参数的右边也必须是默认参数。否则,fun(int i = 0, int j),那么调用fun(3)。3是覆盖i还是赋值给j,无法判断。
    1. 函数重载:函数重载的关键在于函数的参数列表,即特征标(function signature)不同。
        现讨论六种比较有疑惑的函数;按值传递:int fun(int a), int fun(const int a),引用形参: int fun(int & a), int fun(const int & a),指针形参 int fun(int * a), int fun(const int *a)。指针形参和其它两组显然可以重载。
      • 对于按值传递int fun(int a)和int fun(const int a)无法重载。因为显然编译器在调用fun(a)时,无法区别选择哪一个。
      • 按值传递和引用形参无法进行重载。
        int fun(int & a)和int fun(int a)以及int fun(const int a)是同样的。调用fun(a)时,编译器无法区别调用的是哪个函数。因此会报错,重复定义函数。
      • 引用形参的const和非const形式,指针形参的const和非const形式均可以视为重载。因为const实参只能赋值给const形参,所以编译器对const实参调用const形参,对非const实参调用非const形参。
      • 重载是对参数列表进行重载,而不是返回类型。所以参数列表相同,返回类型不同不算重载,编译报错。参数列表不同,则返回类型也可以不同。

        当实参与形参的类型不匹配时,会按照一定的规则进行自动类型转化。面对重载的函数,如果按照自动类型转换规则,只有一个函数适合,那么编译器就调用那个函数。如果有多个函数适合,编译器报错,有二异性。

      void fun(double x);                                    *          void fun(double x);            
      void fun(int x);                                       *          void fun(float x);
      {                                                     *          {
          float x = 9.0;                                     *               int x = 9; 
          fun(x);                                            *               fun(x);
      }                                                      *          }
      float转换为double优于转换为int,所以编译器会调用fun(double)            int转换为float和double均可以,所以编译器报错,调用fun会产生二异性。
  5. 函数模板:适用:需要多个将同一种算法用于不同类型的函数。函数模板并不是减少了生成函数的个数,最终编译出来的函数个数和指令还是不变的。它只是为函数的定义在编写上提供遍历。
    函数模板包括三方面:函数模板的隐式实例化(implicit instantiation),后期的C++也支持显式实例化(explicit instantiation),显式具体化(explicit specialization)。格式如下
    template <class T> void fun(T a); //模板原型,typename和class两个关键字可以互换
    template void<int> fun(int a);//显式实例化的原型


    template<> void fun (int a); //
    template <> void fun(int a);//上面和这个两者都是显式具体化的原型,是等价的声明
    模板原型和模板定义与函数原型和函数定义一样。在使用模板前必须声明模板原型。模板也可以重载,同名模板的参数列表不同,这与函数重载一样。由此,对于一个函数名,可以有非模板函数,模板函数,和显式具体化以及它们的重载提供的各种版本。调用函数时它们的优先级大致为非模板函数>显式具体化>显式实例化>模板函数。对于非模板函数重载的版本,根据形参和实参最佳匹配原则确定调用版本。对于模板重载函数,根据部分排序规则确定调用版本,就是找出最具体的模板。如果能找出一个最佳函数,则编译成功。否则,编译会报出二异性错误,函数无法解析。函数的选择是个非常细致和复杂的过程,可以找具体资料查询。
posted @ 2014-01-14 11:23  而远之  阅读(1234)  评论(0编辑  收藏  举报