【C++课程笔记01】类和对象

使用结构体有一个问题,可以在结构体外部任意获取和修改其成员变量,带来了很大安全隐患,为此 C++ 引入了一个全新的概念,类!类相比结构体,除了有成员变量外,还可以定义成员函数,调用者通过成员函数来获取或修改成员变量,以达到保护的作用;当然类还有其它很多的功能和好处,其中一点就是面向对象的思想。

结构体

C 语言中使用结构体 struct 来实现可以包含多个数据成员的复杂数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Stu
{
char name[4];
int born;
bool male;
};
int main()
{
struct Stu stu;
strcpy(stu.name, "li");
printf("name: %s\n", stu.name);
return 0;
}

使用结构体的时候需要注意几点:

  1. 结构体定义不要忘了最后的逗号,声明语句也是一条语句,每条语句都必须以逗号结束;
  2. 使用结构体的时候要写完整的类型名称 struct Stu,省略 struct 关键字会报编译错误 unknown type name ‘Stu’; use ‘struct’ keyword to refer to the type
  3. 如果要省略 struct 关键字,可以使用类型重命名关键字来结构体类型起个别名,如 typedef struct Stu Stu;
  4. 可以把声明语句和重命名语句合二为一,在结构体定义的时候就给它一个别名。
1
2
3
4
5
6
typedef struct Stu
{
char name[4];
int born;
int male;
} Stu;

C++ 中可以不用使用 typedef 重命名而直接省略 struct 关键字,但在 C 中不行。

完整代码示例:struct.c

使用结构体有一个问题,可以在结构体外部任意获取和修改其成员变量,带来了很大安全隐患,为此 C++ 引入了一个全新的概念,类!类相比结构体,除了有成员变量外,还可以定义成员函数,调用者通过成员函数来获取或修改成员变量,以达到保护的作用;当然类还有其它很多的功能和好处,其中一点就是面向对象的思想。

C++ 中的结构体也支持成员函数定义,但在 C 中不行;即使如此,结构体的功能也很有限,只在比较简单的情形下使用,遇到复杂的需求还是得使用类来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Student
{
public:
char name[4];
int born;
bool male;

void setName(const char* s)
{
strncpy(name, s, sizeof(name));
}
};

int main()
{
Student yu;
yu.setName("Yu");
std::cout << yu.name << std::endl;

strncpy(yu.name, "Li", sizeof(yu.name));
std::cout << "It's name is " << yu.name << std::endl;

return 0;
}

这个例子中使用 setName 成员方法来修改成员变量 name,但同样也可以直接访问修改,因为其成员变量也是公有的,那这就起不到保护的作用了,这时候就需要使用访问修饰符 access sepcifier

完整代码示例:firstclass.cpp

访问修饰符

类的访问修饰符有三个:public, private, protected

  • public 修饰的成员是公有的,无论是类里面还是类外面都可以直接访问;
  • private 修饰的成员是私有的,只有在类里面才能访问,类外面访问是非法的;
  • protected 修饰的成员可以在类里面和它的子类里面访问,类外面访问是非法的。

上一个例子中,类的所有成员变量和成员方法都被声明成 public,所以起不到保护的作用,比较好的做法是把成员变量声明成私有的,成员方法声明成公有的,这样在类外面就只能通过成员方法来访问修改成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Student
{
private:
char name[4];
int born;
bool male;

public:
void setName(const char* s)
{
strncpy(name, s, sizeof(name));
}
const char* getName() { return name; }
};

int main()
{
Student yu;
yu.setName("Yu");
std::cout << "It's name is " << yu.getName() << std::endl;

return 0;
}

如果没写修饰符,则默认为 private,所以本例中的 private 关键字可以省略,三个成员变量同样是私有的,只是一般不会省略,这样可读性会强一点,所有私有的成员和公有的成员一目了然。

1
2
3
4
5
class Student
{
char name[4];
const char* getName() { return name; }
};

如上例所示,如果整个类都没有修饰符,则所有成员变量和成员方法都是私有的,这时候无论是通过 stu.name 还是 stu.getName() 来访问都是非法的,一般都不会这么写的,因为类定义出来就是使用的,定义一个“全私有”的类是毫无意义的。

完整代码示例:access-attribute.cpp

内联函数

类的成员方法在类里面声明,方法定义则可以在类里面也可以在类外面,如果在类里面定义则是一个内联函数,在类外面定义也可以通过关键字 inline 将其定义成内联函数。内联函数在编译的时候会使用函数体替换函数调用,优点是执行速度快(减少了函数调用的开销),缺点是代码体量变大,特别是调用函数体比较大或者调用次数多的时候。一般在函数体较小的地方使用内联函数,比如成员变量的 Getter 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student
{
//...
public:
const char* getName() { return name; }

void setName(const char* s);
void printInfo();
};

inline void Student::setName(const char* s)
{
strncpy(name, s, sizeof(name));
}

void Student::printInfo()
{
//...
}

这个例子中 getName 定义在类里面,setName 定义在类外面但加了 inline 关键字,所以这两个函数都是内联函数,而 printInfo 定义在类外面且没有 inline 关键字,所以不是内联函数。

完整代码示例:inline.cpp

多文件

在前面的例子中类的声明和实现放在一个文件里,更好的做法是把声明和实现分开,分别放在头文件 *.h/*.hpp 和实现文件 *.cpp/*.cxx 中。

避免头文件被重复包含

在头文件中要声明只能被包含一次,避免其被多个文件重复包含,有 2 个方案:

  1. 在头文件的第一行加上 #pragma once
  2. 通过宏定义来检查,如下:
1
2
3
4
#ifndef __STUDENT_H__
#define __STUDENT_H__
//...
#endif

#include <> “”

  • #include <> 引用的是编译器的类库路径里面的头文件;
  • #include "" 首先在当前路径查找头文件,如果找不到再去编译器的类库路径找,当前路径是指 .cpp 文件所在的路径,不是工程的根目录,如果 .h 文件和 .cpp 文件不在一个目录,比如:
1
2
3
4
5
|-project
|-include
|-test.h
|-classes
|-test.cpp

则在 test.cpp 中要写 #include "../include/test.h" 才能正确引用 test.h 文件。
一般引用系统类库中的头文件用 <>,引用自己工程的头文件用 “”,虽然 “” 也可以引用系统类库头文件,但会先查找当前路径,找不到再去系统类库查找,其效率当然不如直接写 <>。

使用 cmake

单个文件可以使用 gcc/g++ 编译器直接编译链接,如 g++ test.cpp,但多文件的时候就得借助其它工具了。除了使用像 vs 之类的大型 IDE 之外,还可以使用开源、跨平台的工具集 cmake

首先,我们新建一个 build 目录用于存放生成的中间文件和可执行文件,当前的目录结构如下:

1
2
3
4
5
6
7
8
|-project
|-build
|-include
|-student.h
|-classes
|-student.cpp
|-main.cpp
|-CMakeLists.txt

然后编辑配置文件 CMakeLists.txt,注意这个文件名不能修改,否则 cmake 无法识别。

1
2
3
4
5
6
7
8
# define cmake required version
cmake_minimum_required(VERSION 3.10)

# define project name
project(student)

# define executable filename and source file
ADD_EXECUTABLE(student main.cpp classes/student.cxx)

接下来开始使用 cmake 来生成我们的工程,首先进入 build 目录,因为要让文件生成在这里,然后执行 cmake <path>pathCMakeLists.txt 所在的目录,之后生成对应的配置文件和中间文件,最后再执行 make 命令进行编译链接。完整的命令如下:

1
2
3
$ cd build
$ cmake ..
$ make

完整代码示例:multi-files