本目展示一个linux环境下系统编程的一个demo。主要是为了讲解项目下文件夹的布局以及makefile的写法。
程序的源代码来自于书籍《Accelerated c++》一书当中第四章的例子。
布局
一个基本的项目主要是以下5项构成:
- src:存放项目的源文件。目录可以细分。
- inc:存放项目的头文件。目录可以细分;
- bin:存放二进制可执行文件。
- Makefile:Makefile文件
- run.sh:运行脚本,命令行参数通过这个脚本传递。
源码
// 路径:./src/main.cpp
#include <vector>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <ios>
#include <iomanip>
#include <string>
#include <algorithm>
#include "Student/StudentInfo.h"
#include "grade/grade.h"
int main( int argc, char* argv[] ) {
if( argc != 2 ) {
std::cerr << "Usage: argv[1] is input path." << std::endl;
return -1;
}
std::vector<StudentInfo> vec_stu;
std::ifstream fin;
fin.open( argv[1] );
if( !fin.is_open() ) {
std::cerr << "Can not open the file!" << std::endl;
return -1;
}
int maxlen = -1;
StudentInfo s;
while( read_stu( fin, s ) ) {
vec_stu.push_back(s);
int name_sz = s.name.size();
maxlen = (name_sz > maxlen)?name_sz:maxlen;
}
std::sort( vec_stu.begin(), vec_stu.end(), compare );
try {
std::streamsize prec = std::cout.precision();
std::cout << std::setprecision(3);
int sz = vec_stu.size();
for( int i = 0; i < sz; ++i ) {
double total = grade( vec_stu[i].mid, vec_stu[i].fin, vec_stu[i].hw );
std::string space(maxlen - vec_stu[i].name.size() + 1, ' ');
std::cout << vec_stu[i].name << space << total << std::endl;
}
std::cout << std::setprecision(prec);
}
catch( std::domain_error e ) {
std::cerr << e.what();
fin.close();
return -1;
}
fin.close();
return 0;
}
// 路径:./src/median.cpp
#include <vector>
#include <algorithm>
#include <stdexcept>
#include "median/median.h"
double median( std::vector<double> vec ) {
if( !vec.size() )
throw std::domain_error( "vec is empty!" );
std::sort( vec.begin(), vec.end() );
int sz = vec.size();
return (sz & 1)?vec[sz/2]:1.0/2*( vec[sz/2] + vec[sz/2-1] );
}
// 路径:./src/grade.cpp
#include "grade/grade.h"
#include "median/median.h"
#include <vector>
#include <stdexcept>
double grade( double mid, double fin, double med ) {
return mid * 0.2 + fin * 0.4 + med * 0.4;
}
double grade( double mid, double fin, const std::vector<double>& hw ) {
int sz = hw.size();
if( !sz )
throw std::domain_error( "Empty hw!" );
double med = median(hw);
return grade( mid, fin, med );
}
// 路径:./src/StudentInfo.cpp
#include "Student/StudentInfo.h"
bool compare( const StudentInfo& a, const StudentInfo& b ) {
return a.name < b.name;
}
std::istream& read_stu( std::istream& is, StudentInfo& s ) {
if( is ){
is >> s.name >> s.mid >> s.fin;
read_stu_hw( is, s.hw );
}
return is;
}
std::istream& read_stu_hw( std::istream& is, std::vector<double>& hw ) {
if( is ) {
hw.clear();
double x = 0.0;
while( is >> x ) {
hw.push_back(x);
}
is.clear();
}
return is;
}
// 路径:./inc/median
#ifndef MEDIAN_H_
#define MEDIAN_H_
#include <vector>
double median( std::vector<double> );
#endif
// 路径:./inc/grade/
#ifndef GRADE_H_
#define GRADE_H_
#include <vector>
double grade(double, double, double);
double grade(double, double, const std::vector<double>&);
#endif
// 路径:./inc/StudentInfo
#ifndef STUDENT_INFO_H_
#define STUDENT_INFO_H_
#include <string>
#include <vector>
#include <istream>
struct StudentInfo {
std::string name;
double mid;
double fin;
std::vector<double> hw;
StudentInfo() : mid(0.0), fin(0.0) {}
};
bool compare( const StudentInfo&, const StudentInfo& );
std::istream& read_stu( std::istream&, StudentInfo& );
std::istream& read_stu_hw( std::istream&, std::vector<double>& );
#endif
EXE_DIR := ./bin
INC_DIR := ./inc
SRC_DIR := ./src
TARGET := $(EXE_DIR)/main
OBJS := $(wildcard ./src/*.cpp)
CFLAGS := -Wall -g -I$(INC_DIR) -std=c++11
CC := g++
$(TARGET):$(OBJS)
$(CC) -o $@ $(OBJS) $(CFLAGS)
clean:
rm -rf $(TARGET)
总结
我觉得需要特别注意的有:
- 头文件路径的写法。由于makefie文件 -I /inc,只是链接了头文件目录,但是头文件有各自的子目录,所以写include命令的时候需要包含子目录。这也是leveldb的写法。
- 执行目录不是源文件的目录,这点要注意。