本节讨论C++的程序结构。
在类外部定义函数
之前将函数在类中定义,现在首先将函数定义到类的外部,仅在类中申明。
注意定义在外部的函数,函数名前面需要加上Person::,表示属于Person类,其他规则和在类中定义一致。
单模块处理
当函数内容比较简短时,写在类中也可以,但是如果篇幅较大,最好还是在外部实现,这样代码会更简洁些。
假设一个团队在做某个设备:
- 由A实现Person类(相当于Person模块);
- 由B实现main,B会在main中使用Person类,但是不会关心Person类的具体实现,只关心怎么使用这个类;
那么,A需要创建两个文件:
- person.h
- person.cpp
其中头文件 person.h 需要提供给B使用,person.cpp 则由A自行保管维护即可。
B只需要实现一个文件 main.cpp 即可,在 main.cpp 文件中 include person.h 文件,然后使用person类。
person.h
person.h 的内容如下,在头文件中,只是定义了一个Person类。
通过person.h头文件,可以看到,Person类中的有私有成员name,age,也有公有成员setName函数和printfInfo函数。
这样,使用Person类的人就能很方便的知道,要想设置name,就需要通过setName函数,而不需要关心具体的函数实现。
#ifndef __PERSON_H_
#define __PERSON_H_
class Person {
private:
char *name;
int age;
public:
void setName(char *name);
void printfInfo();
};
#endif
person.cpp
person.cpp的内容如下,在cpp文件中,include person.h文件,然后实现Person类中的函数。
#include <stdio.h>
#include "person.h"
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
void Person::setName(char *name) {
this->name = name;
}
void Person::printfInfo() {
printf("name = %s, age = %d\n", name, age);
}
main.cpp
在main.cpp中,通过person.h将Person类包含进来,然后就可以使用person类了。
#include <stdio.h>
#include "person.h"
int main(int argc, char **argv)
{
Person per;
per.setName("zhangsan");
per.printfInfo();
}
Makefile
通过一个Makefile来实现编译和清除。
$^ 表示所有的依赖,$< 表示第一个依赖。
target=person
$(target): main.o person.o
g++ -o $@ $^
%.o : %.cpp
g++ -c -o $@ $<
clean:
rm -f *.o $(target)
编译出来执行elf文件,结果符合预期,程序改写成功。
多模块处理
假设需要新增一个功能模块Dog,由C负责,那么团队的分工则变为:
- 由A实现Person类;
- 由B实现main,B会在main中使用Person/Dog类,但是不会关心Person/Dog类的具体实现,只关心怎么使用Person/Dog类;
- 由C实现Dog类;
类似的,C需要实现两个文件:
- dog.h
- dog.cpp
与 person 类似,在dog.h中声明一个dog类,在dog.cpp中实现dog类。
dog.h
#ifndef __DOG_H_
#define __DOG_H_
class Dog {
private:
char *name;
int age;
public:
void setName(char *name);
void printfInfo();
};
#endif
dog.cpp
#include <stdio.h>
#include "dog.h"
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
void Dog::setName(char *name) {
this->name = name;
}
void Dog::printfInfo() {
printf("name = %s, age = %d\n", name, age);
}
main.cpp
修改main.cpp,将dog.h包含进来,然后就可以使用Dog类了。
#include <stdio.h>
#include "person.h"
#include "dog.h"
int main(int argc, char **argv)
{
Person per;
Dog dog;
per.setName("zhangsan");
per.printfInfo();
dog.setName("wangcai");
dog.printfInfo();
}
命名空间(namespace)
假设,需要在person.cpp和dog.cpp中,分别实现一个printVersion函数,打印对应模块的版本号。
在person.h和dog.h中申明printVersion函数,注意并不是在类中声明。
然后在main.cpp中调用。
此时,编译程序会报错,这是由于在dog.h和person.h中都有定义printVersion函数,编译器不知道要调用哪一个printVersion。
这个问题就涉及到命名空间了。
在实际工程中,可能会遇见相同名称的函数,当编译器遇见相同名称的函数时,需要根据命名空间来决定使用哪个函数。
首先修改person.h,dog.h,person.cpp,dog.cpp,分别设置两个作用域A和C。
这时候编译就会报错,因为没有标明使用的是哪个域,编译器会报找不到类型声明。
为了方便起见,将多余的代码先注释掉。
函数前面的A::和C::分别表示这个函数属于A和C的命名空间,声明命名空间后,代码可以编译成功,执行结果也符合预期。
类似的,也可以通过using namespace来决定要使用哪个命名空间,这时候,不冲突的对象可以不用加A::和C::,冲突的函数与之前一样,需要声明使用的命名空间是哪个。
如果只要要使用某个命名空间中的某个对象,而不是全部的话,也可以单独using那个类。
执行结果和之前是一样的。
小结
程序结构如下:
- 类定义(.h) / 类实现(.cpp);
- 命名空间;
- 在.h/.cpp文件中,使用namespace a { ... }设置命名空间;
- 在调用者源文件中:
- 直接使用:a::fun, a::fun2
- using声明:using a::fun; //以后调用fun即表示a::fun
- using编译:using namespace a;//以后直接调用fun,fun2即可
使用iostream
最后,再修改一下代码。
在C++中,通常使用 iostream 而不是stdio.h。
使用iostream,需要用cout替换printf,并且需要使用命名空间std。