Essential C++ 第二章-面向过程的编程风格
第一章的程序全部卸载了main()函数里面,在程序规模比较大的情况下,这是不合实际的,因此需要将”共通“的功能抽取出来,携程独立的函数,以下为优点:
1. 使用一连串函数进行调用操作,取代重复的程序代码,增强程序可读性;
2. 可以在不同程序中调用同一个函数,提高复用性;
3.便于团队合作
Tips: 如果编写的程序和预期运行不一致,可以使用以下方式debug:
1. 使用断点,一步一步检查
2. 使用ofstream语句,将每一步执行结果打印到一个文件内,查看文件发现哪里有问题。
2.1 如何撰写函数: 定义函数有以下步骤(功能函数和main主程序在同一个cpp文件内):
- 1.指定函数的返回值类型(也就是经过函数计算之后,输出的结果): 如int...若无返回值,需要用void占位;
- 2.函数的名称,一般需要直观表现出函数功能,如: math_calcu();
- 3.函数的输入参数列表(输入参数),实际上就是调用函数时,输入的数值 如: math_calcu(int x,int y,string x)
- 4.函数主体,就是函数功能实现的主体 ,使用{}括起来。
- 注意! 若函数的返回值设定的不是void,那么在函数必须以return结束,否则会出错
- 若函数设定的返回值是void,那么函数需要以return; 结尾
// P2.1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//简单的函数编写:fibo数列(斐波那契数列)
#include <iostream>
using namespace std; //指定运行空间
bool fibon_elem_2(int, int &); //此处为函数声明 (forward declaration)
//需包含:函数返回值类型bool,函数名称fibon_elem
//输入参数类型 (int, int&)-->整型和指针
int main()
{
int pos;
cout << "Please input the position: \n";
cin >> pos;
int elem;
if (fibon_elem_2(pos, elem)) {
cout << "The element in the position is: " << elem;
}
else{
cout << "The position info was wrong: \n" ;
}
return 0;
}
bool fibon_elem_2(int pos, int &elem)
{
if (pos <= 0 || pos > 1024) {
elem = 0; return false;
}
if (pos == 1 || pos == 2) {
elem = 1; return true;
}
elem = 1;
int n_2 = 1, n_1 = 1;
for (int ix = 3; ix <= pos; ++ix) {
elem = n_2 + n_1;
n_2 = n_1;
n_1 = elem;
}
return true;
}
2.2 调用(invoking)一个函数,以及传址与传值:
- 以冒泡排序为例,需要使用子函数,进行排序,并将排序后的数值保存在vector里面;
- 要分清:int x=1024 (定义一个对象); int *p=&x (指针指向一个对象); int &rval=ival (referece 化身),代表一个int对象,的关系。
- 使用传值前提:如果在调用函数进行运算并且要对全局变量进行修改时,使用传址&,否则使用函数的返回值即可;
- 区分以下几个的意义:传址和传值的区别,以以下程序为例:
void swap_1(int val1,int val2){ //寻值而非寻址
int temp = 0; //导致:这个函数仅对函数内部数据进行了更改
temp = val2;
val2 = val1;
val1 = t3mp;
}
void swap_2(int &val1,int &val2){ //寻址
int temp = 0; //这个函数计算完成之后!!
//会对传入函数的地址内信息进行修改!!
temp = val2;
val2 = val1;
val1 = t3mp;
}
void bubble_sort(vector<int> &vec) { //&表示直接操作这个地址内存放的数值
for (int ix = 0; ix < vec.size(); ++ix) {
for (int jx = ix + 1; jx < vec.size(); ++jx) {
if (vec[ix] > vec[jx]) {
swap(vec[ix], vec[jx]);
}
}
}
}
int main()
{
int ia[8] = { 8,34,3,13,1,21,5,2 }; //新建一个有8个数值的数组
//并通过下边的方式对vector进行赋值
vector<int> vec(ia,ia+8); //
cout << "The vector before sorting is :\n" << endl;
display_vector(vec);
bubble_sort(vec);
cout << "The vector after sorting is :\n" << endl;
display_vector(vec);
}
2.3 生存空间(scope)及生存范围(extent)
- 生存空间scope(全局及局部变量):
生存空间分为局部生存空间(局部变量)(local scope)(仅在函数体有效)(需要初始化赋值)
与全局生存空间(全局变量)(file scope)(在程序内有效-在函数体外赋值)(未赋值时,自动赋值为0)
2. 动态内存管理(动态变量):不管时local scope还是file scope,系统都会自动进行内存分配; 但是有一种存储形式不行;
称为动态范围:dynamic extent--有时也被成为堆内存,此种类型内存必须由程序员管理。
管理方法为: 使用new表达式进行配置,使用delete表达式完成释放;
注意!!创建好动态范围的变量使用完毕后,必须要及时将其delete,否则会出现memory leak (内存泄漏)
// dynamic extent 动态范围
//语法为: 变量名=new Type (initial_value);
//例如:
pi= new int (1024); //创建一个初始值为1024的名为pi的变量
//释放:
delete pi;
3. 局部静态对象(local static objects)--实际上就是在系统中创建容器,若后边用到已有内容,就不用从头开始生成对象了
-
Tips1: 为了节省函数间通信问题而将所有对象都定义在file scope里面,过于冒险,会打破函数之间的独立性,使程序难以理解。
4. Inline 函数(将大函数拆分成小函数,必要时再组合在一起,提高执行效率):
- 目的如下边的例子,我们把fibo原先的函数拆分成了3个不同的子函数,在生成fibon数列时,仍会依次调用这三个函数;
- ->若执行效能不理想,需要将三个函数重新组合成同一个函数运行时,需将这三个函数声明前加上inline即可
- ->最终结果: 虽然三个子函数还是独立的,但是可以组合成一个大的函数体运行(与之前大函数体类似),同时提高复用性
- 重要:一般情况下,适合声明为inline的函数,一般和fibon_elem(),is_size_ok() 一样,体积小但常被调用。
以下为局部静态对象和inline函数例子:尤其要注意:
// C2.5Inline.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//局部静态对象及inline函数
#include <iostream>
#include <vector>
using namespace std;
bool is_size_ok(int size) {
if (size <= 0 || size > 1024) {
cout << "Oops the size is not supported: \n";
return false;
}
else {
return true;
}
}
const vector<int>* fibon_seq(int size) { //此处实际上是一个局部静态对象(常量),返回值是一个地址
const int max_size = 1024;
static vector<int> elems; //在函数体内创建一个静态对象vector(非易失对象),之后进行计算
//局部静态对象与非静态区别:静态对象在函数体结束后不会被清除
if (!is_size_ok(size)) {
return 0;
}
for (int ix = elems.size(); ix < size; ++ix) {
if (ix == 0 || ix == 1) {
elems.push_back(1);
}
else {
elems.push_back(elems[ix - 1] + elems[ix - 2]);
}
}
return &elems; //返回值为计算结束后的vector对象的地址(!静态容器地址)
}
inline bool fibo_elem_checking(int pos, int &elem) {
const vector<int> *pesq = fibon_seq(pos); //根据输入要取出元素的位置,创建数列,并返回地址;
if (!pesq) {
elem = 0; return false;
}
elem = (*pesq)[pos - 1];
return true;
}
int main()
{
int elem = 20;
if (fibo_elem_checking(7,elem)) {
cout << "Successed in generating:";
cout <<"the number is:" << elem ;
}
return 0;
}
5. 重载函数(Overloaded Function)
- 函数的重载主要使用以下方法: 同一个函数名称可以对应不同的参数表(parameter list),也就是说通过设定不同数量和类型的参数,一个函数名可以具有多种不同的功能-->通过控制传入参数的类型,程序自动决定采用哪个函数;
- 注意!!仅能通过输入函数数量,类型来作为采用哪个重载函数的标准,不同的返回值不能作为评判标准。
6. 模板函数+函数重载(Template Functions))
- 主要目的是:当我们输入参数不同,又想使用同一个函数进行处理,此时需要编辑模板函数,然后程序将能够自己判断输入的数值类型,自动生成对应的函数。
template <typename elemType> void display_message(const string& msg, const vector<elemType>& vec) { //根据函数输入的第二个参数,自动匹配elemType数据类型 cout << msg; for (int ix = 0; ix < vec.size(); ++ix) { elemType t = vec[ix]; cout << t << " "; } }
7. 函数指针(positers to Function)-可为程序带来更大弹性
8.头文件
-
由于自定义函数必须经过申明以后才能使用,若一个程序需要运行很多函数时,那就需要非常多的声明;
-
-->此时将全部函数的声明全部存放在一个头文件当中,就比较方便:
-
重要!!在程序中,调用头文件方法:若.h头文件与.cpp文件在同一目录下,那么使用#include "ssss.h"即可
-
-->若在不同的目录下,需要使用#include <xxx.h>进行查找添加