找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4484|回复: 0
打印 上一主题 下一主题
收起左侧

如何学习C++(面向过程编程)

[复制链接]
跳转到指定楼层
楼主
ID:101489 发表于 2016-1-3 02:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
C++支持3种编程方法即“面向过程编程”、“泛型编程”和“面向对象编程”。掌握这3中编程方法,就能够学好C++。
这篇日志谈谈如何用C++进行面向过程编程。
C语言的编程方式就是面向过程编程,将C++与C对比起来学习是掌握C++面向过程编程的一种捷径。
对比1:基本语法
在基本语法上C++和C语言是相同的,C语言有的语法C++都有,而C++有的语法C语言基本全有。如果不考虑泛型编程和面向对象编程,C++在基本语法上与C语言无区别。因此学习过C语言的程序员,可以直接跳过C++基本语法的学习。
对比2:基本数据类型
所谓基本数据类型是指int、float、double等这些类型。相对于C语言,C++增加了bool型。bool型只有两个值true和false,分别表示真和假。
其通常使用的方式如下:
bool flag=(x>10);
if(flag)
   printf("x>10");
else
   printf("x<=10");
当然这是一个示例,实际的情况可能要复杂得多。bool型可以赋值为整型。
例如:
bool flag=10;
bool flag=-10;
bool flag=0;
只要赋值的整型不是0,那么bool型的值就是true,反之是false。
对比3:static变量
C++允许将局部变量定义为static。例如:
int fun()
{
    static int x=0;
    x++;
   return x;
}
如果按照下列方式调用该函数:
fun();
fun();
int x=fun();
问x的值是多少?
答案是3。
其原因在于,一旦一个变量被声明为static,则该变量一直存在于函数的局部,而不会在函数退出时销毁。故此按照上述调用实际上执行了三次x++。
static变量的引入是为了解决全局变量的过度使用。如果C语言没有static变量,那么程序只能这么写。
int x;
int fun()
{
    x++;
    return x;
}
此时x是一个全局变量。
对比4:变量初始化
C++允许一种新的变量初始化方法,如下:
int a(10);
此语句与C语言中的:
int a=10;
等价。
引入这种新语法的原因是为了与对象的初始化统一。例如:
ifstream f("c:\\test.txt");
该语句定义了对象f,f是一个输入文件流对象(关于对象的问题后面再谈)。由于对象的初始化只能采用上面的方式,因此索性引入了
int a(10);
这样的语法。否则傻瓜编译器可能就要出现二义性错误了。
换言之,这种新语法完全可以不用,用也没有坏处,反正编译器认识。
对比5:默认函数参数
int fun(int x=0)
{
    x++;
    return x;
}
上述函数使用了默认参数,即给参数x一个默认值0。调用函数时
int y=fun();
int y=fun(0);
两者的结果完全等价,y的值都是1。而
int y=fun(10);
调用的结果就是11。
对比6:引用
下列代码中,b称为a的引用。
int a=10;
int &b=a;
此时若执行如下代码:
b=b+10;
请问a的值是多少?答案是20。
为什么修改b,同时也会修改a呢?因为b是a的引用。也就是b和a是同一个东西。
上述代码可以换成指针形式,即:
int a=10;
int *b=&a;
*b=*b+10;
其结果仍然是把a修改为了20。
那么为什么要引入引用呢?因为引用比指针更加安全。如果我们使用指针,那么就可能出现下面的情况:
int a=10;
int *b=&a;
b=b+10;
这个程序员把代码写错了,但是编译器并不会发出任何警告。因为在编译器眼中,这个代码是完全正确的。
b=b+10;
意味着b指向的地址后移10,此时b已经不再指向a,而指向了一个我们不知道的位置。如果把这个代码改成下面的形式,结果更是灾难性的。
int a=10;
int *b=&a;
b=b+10;  //本意是写*b=*b+10,因为疏忽忘记了*
*b=100;
按照程序员的本意,此时a和b都应该变成100。但很遗憾上述代码不仅a仍然是10,并且还修改了一个不确定位置的值为100。若修改的位置正好是你的银行存款,原本的数字是10000000,而现在修改为了100,你甚至都不知道是怎么修改的。你说这不是灾难性的吗?
如果将指针改为引用,情况就好得多了。
换言之指针类型可以直接操作地址值,而引用是不行的
引用的另外一个特点是不允许空引用。例如
int&b;
这样的声明就是错误的,而指针是可以为空的。
int *b;
是合法的。
扯一句题外话,java的引用和C++的引用也是不同的。java的引用是可以为空的。因此java的引用类型等价于C++不允许修改地址的指针。
引用的一个重大用途是用于参数传递。先看下列代码:
int fun(vector<int> v)
{
 v[0]++;
 return v[0];
}

int main()
{
 vector<int> v(3);
 v[0]=1;
 v[1]=2;
 v[2]=3;

 int y=fun(v);
 return 0;
}
其中vector是C++标准库中的新类型。大家可以先把它认为是数组。
上述代码,将v传递到fun中,并在其中将0号元素++后返回。程序看似没有问题,但实际上隐藏了巨大的隐患。
vector<int> v(300000000);
v[0]=1;
v[1]=2;
v[2]=3;
.....
v[300000000]=....;
 int y=fun(v);

把程序修改成上面就可以看出问题了。问题在于v的大小为300000000,这个长度简直是太大了。但调用fun的时候,编译器会将v拷贝一份,然后将这个拷贝传递到函数fun。可以想象一下这么大的一个数据,拷贝是不是要花时间?拷贝是不是要消耗内存?怎么避免这个问题呢?
事实上C语言可以用指针解决这个问题,代码修改后如下
int fun(vector<int> *v)
{
 (*v)[0]++;
return (*v)[0];
}

int main()
{
vector<int> v(3);
v[0]=1;
v[1]=2;
v[2]=3;

int y=fun(&v);
return 0;
}
此时函数调用时传递的就是指针,不再是整个值,也就不存在拷贝的问题。用引用同样可以解决这个问题。
int fun(vector<int> &v)
{
   v[0]++;
   return v[0];
}

int main()
{
vector<int> v(3);
v[0]=1;
v[1]=2;
v[2]=3;

int y=fun(v);
return 0;
}
从代码上看是不是引用更友好一些呢?
但是我们也发现引用和指针有一个副作用,那就是v[0]的值会在fun中被修改。因此代码应该修改一下:
int fun(vector<int> &v)
{
   return v[0]+1;
}

但我们知道程序员是会犯错误的,尽管我们知道应该如此写,但也许一不小心就犯错了。有解决的办法吗?那就是给引用加上const。
对比7:const变量
C++引入了const关键字,用于定义常量。例如:
const int a=10;
此时a称为一个整型常量,即a的值不能修改,若修改则编译器会报错。
在参数传递时可以为参数加上const,以避免参数被修改。如下:
int fun(const vector<int> &v)
{
    v[0]++;    //此处将报错
   return v[0];
}

const的另一个有趣问题在于const放的位置。
const int a=10;
int const a=10;
都是合法的。那么在使用上有区别吗?也没有区别,都是int常量。可是如果将int换成引用或者指针情况就大不一样了。
cont int *a;  //常量指针,a指向的类型为const int, a的类型为*
int const *a; //常量指针,a指向的类型为int const  , a的类型为*
int* const a; //指针常量,a指向的类型为int, a的类型为*const
const int* const a; //指向常量的指针常量,a指向的类型为const int, a的类型为*const
有了*号之后组合数量猛增,那么引用情况也是类似的
const int &a; //常量引用,a引用的类型为const int, a的类型为&
int const &a; //常量引用,a引用的类型为int const, a的类型为&
int& const a; //引用常量,a引用的类型为int, a的类型为&  const
const int& const a; //指向常量的引用常量,a引用的类型为const int, a的类型为& const
对初学者而言这实在是很复杂,不过了解一下编译器的原理,就很容易搞清楚。
说白了上面的代码都是在定义变量a,a是引用或者指针,引用就是&,指针就是*。对于引用或者指针,那么都有引用或者指向的变量类型。
以“&”和“*”为分隔符,“&”和“*”前面的就是引用或者指向的变量类型。
而在“&”和“*”之后的符号则说明,变量a本身是不是一个const。
搞明白了这样的原则,那么下面的类型就不是很变态了。
const int** &const a;
通常如此变态的用法只有在考试时出现,如果是在实际项目中谁敢这么用,那么就有开除的风险了。
对比8:new和delete
new和delete是非常重要的操作符,用于动态内存管理。C语言里面有malloc和free两个函数与之对应。
但很多C语言课程讲到malloc和free的时候都直接跳过不讲,很多学习C语言的学生也通常没有学习过内存管理。
因此new和delete尽管很重要但很多学生在大学四年内都无法掌握,尽管这是实际应用中非常重要的。
这也难怪,如果我们总是做一些计算水仙花数的题目,是不需要用到内存管理的。
假设现在有一个需求:将硬盘上的50MB的数据读取到程序中,并保存在变量a里面。
我们不管怎么读取硬盘数据,仅考虑变量a应该怎么定义。
  unsigned char  a[1024*1024*50];   //事实上这么写可能会出错,因为可能不允许定义这么大的数组哦
应该是这样吧,一个unsigned char是8位,也就是1个byte,1024*1024*50个byte刚好50MB。
现在考虑一个复杂的情况,如果文件的大小事不确定的,又应该如何定义变量呢?
unsigned char a[SIZE];
其中SIZE表示文件的大小(字节数),但问题在于我们根本不知道SIZE有多大。如果SIZE是一个变量,上述定义是无法编译的。
因此就必须有一种动态分配内存的机制:
unsigned char *a=new unsigned char[SIZE];
此处的SIZE可以是一个变量。
我们看看下面代码的执行效果:
unsigned char *a=new unsigned char[1024*1024*50];
while(true);
动态分配50MB的内存,同时循环不退出,这是为了便于观察系统内存的变化。看下图

注意看Demos.exe*32这个进程,它占用了51,748K内存,也就是大约50MB内存,换言之分配50MB是成功的。

程序修改为如下形式:

unsigned char *a=new unsigned char[1024*1024*100];
while(true);
 

 

没错分配了100MB内存。下面还有一个代码:

while(true)

{

  unsigned char *a=new unsigned char[1024*1024*100];

}

感兴趣的可以自己试试,此代码可以测试你的系统在几秒内蓝屏。我是不会测试这个代码的。

这个代码会在循环中不断的分配100MB的内存,很快内存就会分配完,然后机器挂掉。

有人也许会有疑问:a不是一个局部变量吗?再次循环的时候前一个a变量不是销毁了吗?

的确a会不断的产生和销毁,但那是变量a销毁。a是一个指针,a所指的对象并没有销毁。

若要销毁分配的内存可以如下做:

while(true)

{

   unsigned char *a=new unsigned char[1024*1024*100];

   //do something

   delete a[];

}

这个程序的执行结果如下图:
时刻A

 时刻B

 

时刻A和B的内存是不同的,这说明程序在动态分配内存,同时内存不会一直增加,由于有delete释放内存,因此程序的内存在100MB以内(为什么是变化的?自己考虑吧)
new和delete比较容易混淆的地方是它们的另外一种用法:
int *a=new int(10);
delete a;
=============
int *a=new int[10];
delete []a;
上述代码有一个非常细微的差别
new int(10);和new int[10];
delete a;和delete []a;
解释一下
int *a=new int(10);
表示动态分配了1个int型的内存,并将内存的值初始化为10,然后将地址赋值给指针变量a。
delete a;
用于删除指针变量a。
===========================
int *a=new int[10];
表示动态分配了10个int型的内存,并将内存的首地址赋值给指针变量a。
delete []a;
用于删除指针变量a。
============================
换言之
new 类型(参数)用于动态生成单个变量,并赋初值
delete 变量 用于删除
new 类型[数量] 用于分配多个内存空间
delete []变量 用于删除
 
关于new和delete另一个需要记住的地方是new之后必须delete,否则就会内存泄漏。
如果你的程序开始需要new,那么很快你也会掌握delete。
但如果你的程序从来都不需要new,那么你也不会明白delete。
如果你的C++程序从来没有new和delete,那么没有哪个公司敢雇用你,除了让你当清洁工。
 
对比9:名字空间
在C++中如果想使用标准库中的类和函数就会用到名字空间。例如:
#include<iostream>
using namespace std;   //这就是名字空间
int main()
{
    cout<<"hello world"<<endl;
    return 0;
}
其中std是名字空间,using namespace表示使用std这个名字空间。
关于名字空间,一般正常的C++书籍都会介绍,这里就不说了。如果你的C++书籍没有解释名字空间,那么建议换一本书(这是有可能的,特别是谭浩强之类的书)。
比较奇怪的一点是,尽管名字空间这个技术还算有用,并且标准库中也用了,但在实际编程中,大家还很少用 。也许C++程序员现在多数都是些LIB和DLL,名字冲突的概率比直接写源码低的原因吧。
对比10:inline
一个函数用inline修饰就成为内联函数,如下:
inline void fun()
{
}
inline是给编译器用的。如果一个函数声明为inline,那么编译器会在调用该函数的地方直接展开函数(而不是函数调用)。这样会增加函数调用的效率。内联函数可以部分替代C语言的宏定义。
以上只是官方的一种说法。官方其实还有另外一个说法,就是inline不是强制性的。换句话说,编译器可以忽略掉inline。
个人看法,inline还是不inline真的无法提高什么性能,尤其是如果你想用C++写面向对象程序。
对比11:头文件的使用
C++标准库的头文件都是不带.h结尾的。例如
#include<iostream>
#include<string>
#include<vector>
事实上头文件以.h结尾只是一个习惯,对于编译器而言,就算你用.exe结尾,它也照样不管。
对比12:编写自定义头文件
如果你没有编写过自定义头文件,那么说明你的程序还不够大。不够大的含义是:恐怕不到100行。。。。。
但凡大点的程序都是要写自定义头文件的。这有几个原因:1.程序分模块;2.程序分工;3.编译效率;4.编译器限制太大的文件。
不详细解释了。自定义头文件也是项目大到一定程度,自然会写的。
关键是怎么写才正确,这里有几个原则。
原则1:只有函数声明,不能有函数实现
以下代码称为函数声明
void fun();
以下代码称为函数实现
void fun()
{
}
那么函数实现写在什么地方呢?写在一个cpp文件里面。
至于为什么要这么做,就要扯到编译器的实现问题了。总之不这么做,迟早有一天会出错。而且错得让人找不到北。
这里有一个例外情况inline函数的实现是可以放在头文件里面的。但这只是例外。
原则2:变量声明加extern
头文件中声明的变量一般都是全局变量。如果此变量是常量,那么可以不加extern,其他变量是加上的好。
原因也是编译器的实现机制。
全局变量若定义在头文件里面应该加上extern,如下:
//a.h文件
extern int x;
全局变量的初始化则放到一个cpp文件中,例如:
//a.cpp
int x=10;
然后在b.cpp文件中就可以使用x了,如下:
#include"a.h"
int main()
{
     x=20;
}
上述代码如果去掉extern会出错。
但如果去掉extern,并且把初始化放到头文件中,则有可能正确。例如
//a.h文件
int x=10;
==========================
#include"a.h"
int main()
{
x=20;
}
这完全可能编译正确,但也仅仅是由于运气比较好。
如果a.h在多个cpp文件中被引用就会出错的。
原则3:用宏定义避免同一个头文件被多次#include
//a.h文件
int x=10;
==============================
#include"a.h"
#include"a.h"
int main()
{
x=20;
}
注意a.h被#include了两次,x就被定义了两次,不出错才怪呢。
用宏定义就可以避免这种错误。
//a.h文件
#ifndef A_H
#define A_H
int x=10;
#endif
这样无论#include几次,x都只定义1次。
特别注意:如果不采用原则2的写法,即使加上了宏定义,下列代码仍然是错误的。
//b.h
#ifndef B_H
#define B_H
void fun();
#endif
==================
//b.cpp
#include"a.h"
void fun()
{
      x++;
}
===================
#include "a.h"
#include "b.h"
void main()
{
    x++;
}
 
 
 C++的面向过程编程基本上是对C语言的增强,面向对象才是C++的核心内容。如果你用面向对象编程,那么本文的很多内容其实可以忽略。
写完了,大家看看有没有什么遗漏。
 
 
 
 
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表