C++basic 函数与文件

C++基础: 函数和文件

2.1 引入函数

2.2 返回值函数

2.3 void函数

2.4 函数形参与实参

2.5 局部作用域

2.6 为什么函数如此有用,如何有效使用函数

2.7 前置声明与定义

2.8 多文件编程

2.9 名字冲突与引入namespace

2.10 引入预处理器

2.11 头文件

2.12 头文件防护

2.13 如何设计程序

2.x 总结与小测验


章节预览与回顾
Chapter Review

函数是可重用模块
A function is a reusable sequence of statements designed to do a particular job. Functions you write yourself are called user-defined functions.

A function call is an expression that tells the CPU to execute a function. The function initiating the function call is the caller, and the function being called is the callee or called function. Do not forget to include parenthesis when making a function call.

The curly braces and statements in a function definition are called the function body.

A function that returns a value is called a value-returning function. The return type of a function indicates the type of value that the function will return. The return statement determines the specific return value that is returned to the caller. A return value is copied from the function back to the caller – this process is called return by value. Failure to return a value from a non-void function will result in undefined behavior.

The return value from function main is called a status code, and it tells the operating system (and any other programs that called yours) whether your program executed successfully or not. By consensus a return value of 0 means success, and a non-zero return value means failure.


Best Practice don’t repeat yourself 良好编程实践, 不要重复自己, 利用变量和函数来移除冗余代码。

Practice DRY programming – “don’t repeat yourself”. Make use of variables and functions to remove redundant code.

Functions with a return type of void do not return a value to the caller. A function that does not return a value is called a void function or non-value returning function. Void functions can’t be called where a value is required.

A return statement that is not the last statement in a function is called an early return. Such a statement causes the function to return to the caller immediately.

A function parameter is a variable used in a function where the value is provided by the caller of the function. An argument is the specific value passed from the caller to the function. When an argument is copied into the parameter, this is called pass by value.


函数形参与定义在函数体内的变量成为局部变量, 生命周期为变量定义到函数退出时。

Function parameters and variables defined inside the function body are called local variables. The time in which a variable exists is called its lifetime. Variables are created and destroyed at runtime, which is when the program is running. A variable’s scope determines where it can be seen and used. When a variable can be seen and used, we say it is in scope. When it can not be seen, it can not be used, and we say it is out of scope. Scope is a compile-time property, meaning it is enforced at compile time.


前置声明与定义

Whitespace refers to characters used for formatting purposes. In C++, this includes spaces, tabs, and newlines.

A forward declaration allows us to tell the compiler about the existence of an identifier before actually defining the identifier. To write a forward declaration for a function, we use a function prototype, which includes the function’s return type, name, and parameters, but no function body, followed by a semicolon.

A definition actually implements (for functions and types) or instantiates (for variables) an identifier. A declaration is a statement that tells the compiler about the existence of the identifier. In C++, all definitions serve as declarations. Pure declarations are declarations that are not also definitions (such as function prototypes).


多文件分离式编译

Most non-trivial programs contain multiple files.


名字冲突
When two identifiers are introduced into the same program in a way that the compiler or linker can’t tell them apart, the compiler or linker will error due to a naming collision. A namespace guarantees that all identifiers within the namespace are unique. The std namespace is one such namespace.


预处理
The preprocessor is a process that runs on the code before it is compiled. Directives are special instructions to the preprocessor. Directives start with a # symbol and end with a newline. A macro is a rule that defines how input text is converted to a replacement output text.


头文件
Header files are files designed to propagate declarations to code files. When using the #include directive, the #include directive is replaced by the contents of the included file. When including headers, use angled brackets when including system headers (e.g. those in the C++ standard library), and use double quotes when including user-defined headers (the ones you write). When including system headers, include the versions with no .h extension if they exist.


头文件防护

Header guards prevent the contents of a header from being included more than once into a given code file. They do not prevent the contents of a header from being included into multiple different code files.

总结


1、 带返回值函数未返回值将产生未定义行为
A value-returning function that does not return a value will produce undefined behavior

注: 使用未初始化变量也会引起未定义行为


2、临时对象
Introduction to temporary objects

A temporary object (also sometimes called an anonymous object) is an unnamed object that is created by the compiler to store a value temporarily.

There are many different ways that temporary values can be created, but here’s a common one:

#include <iostream>

int getValueFromUser()
{
 	std::cout << "Enter an integer: ";
	int input{};
	std::cin >> input;

	return input; // return the value of input back to the caller
}

int main()
{
	std::cout << getValueFromUser() << '\n'; // where does the returned value get stored?

	return 0;
}

In the above program, the function getValueFromUser() returns the value stored in local variable input back to the caller. Because input will be destroyed at the end of the function, the caller receives a copy of the value so that it has a value it can use even after input is destroyed.

But where is the value that is copied back to the caller stored? We haven’t defined any variables in main(). The answer is that the return value is stored in a temporary object. This temporary object is then passed to std::cout to be printed.

Temporary objects have no scope at all (this makes sense, since scope is a property of an identifier, and temporary objects have no identifier).

Temporary objects are destroyed at the end of the full expression in which they are created. Thus the temporary object created to hold the return value of getValueFromUser() is destroyed after std::cout << getValueFromUser() << ‘\n’ executes.

In the case where a temporary object is used to initialize a variable, the initialization happens before the destruction of the temporary.

In modern C++ (especially since C++17), the compiler has many tricks to avoid generating temporaries where previously it would have needed to. In the above example, since the return value of getValueFromUser() is immediately output, the compiler can skip creation and destruction of the temporary in main(), and use the return value of getValueFromUser() to directly initialize the parameter of operator<<.


3、 前置声明,声明与定义区别
前置声明

#include <iostream>

int add(int x, int y); // forward declaration of add() (using a function declaration)

int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\n'; // this works because we forward declared add() above
    return 0;
}

int add(int x, int y) // even though the body of add() isn't defined until here
{
    return x + y;
}

声明与定义
Declarations vs. definitions

In C++, you’ll frequently hear the words “declaration” and “definition” used, and often interchangeably. What do they mean? You now have enough fundamental knowledge to understand the difference between the two.

声明是告诉编译器变量或函数的类型信息
A declaration tells the compiler about the existence of an identifier and its associated type information. Here are some examples of declarations:

int add(int x, int y); // tells the compiler about a function named "add" that takes two int parameters and returns an int.  No body!
int x;                 // tells the compiler about an integer variable named x
A definition is a declaration that actually implements (for functions and types) or instantiates (for variables) the identifier.

Here are some examples of definitions:

int add(int x, int y) // implements function add()
{
    int z{ x + y };   // instantiates variable z

    return z;
}

int x;                // instantiates variable x

定义也是声明
In C++, all definitions are declarations. Therefore int x; is both a definition and a declaration.

Conversely, not all declarations are definitions. Declarations that aren’t definitions are called pure declarations. Types of pure declarations include forward declarations for function, variables, and types.

变量声明
extern int x;  //变量纯声明
extern int x = 0; //带初值的extern 为声明 与 int x = 0相同

4、 一个定义原则
The one definition rule (ODR)

The one definition rule (or ODR for short) is a well-known rule in C++. The ODR has three parts:

一、 在同一个源文件中,相同作用域中只能有一个定义(函数,变量, 类型,模版)
Within a file, each function, variable, type, or template can only have one definition. Definitions occurring in different scopes (e.g. local variables defined inside different functions, or functions defined inside different namespaces) do not violate this rule.

二、在一个程序中,多个文件中 每个函数和变量只能有一个定义, 除了链接器不能识别的函数和变量可以排除这个规则。

Within a program, each function or variable can only have one definition. This rule exists because programs can have more than one file (we’ll cover this in the next lesson). Functions and variables not visible to the linker are excluded from this rule (discussed further in lesson 7.6 – Internal linkage).

三、 不同文件中, 类型,模版,内联函数, 内联变量可以允许重复定义,只要定义是完全一致的。

Types, templates, inline functions, and inline variables are allowed to have duplicate definitions in different files, so long as each definition is identical. We haven’t covered what most of these things are yet, so don’t worry about this for now – we’ll bring it back up when it’s relevant.
Related content

We discuss ODR part 3 exemptions further in the following lessons:


自定义类型 可以定义在头文件中,被多个源文件包含引入
函数模版也可以定义在头文件中,被多个源文件包含引入

Types (13.1 – Introduction to program-defined (user-defined) types).
Function templates (11.6 – Function templates and 11.7 – Function template instantiation).

内联函数和变量也可以定义在头文件中,被多个源文件包含引入

Inline functions and variables (5.7 – Inline functions and variables).


违反规则1将引起编译器重定义错误

Violating part 1 of the ODR will cause the compiler to issue a redefinition error.

违反规则2将引起链接器报重定义错误

Violating ODR part 2 will cause the linker to issue a redefinition error.

违反规则3将引起未定义错误
嗯,还记得未定义行为有哪些可能引起吗
1、使用未初始化变量
2、待返回值函数未返回值 (有些编译器直接报错误 如vs , gcc不会报错)
3、违反规则3 目前没有好的示例
Violating ODR part 3 will cause undefined behavior.

5、 程序构建时编译器是链接器查找符号过程

When an identifier is used in an expression, the identifier must be connected to its definition.

一、若编译器编译该源文件时没有发现符号的声明或定义,则报告编译错误

If the compiler has not yet seen either a forward declaration nor a definition for the identifier in the file being compiled, it will error at the point where the identifier is used.

二、若定义在同一文件,则编译器直接连接此次定义
Otherwise, if a definition exists in the same file, the compiler will connect the use of the identifier to its definition.
三、若定义存在其他文件,则链接器连接定义
Otherwise, if a definition exists in a different file (and is visible to the linker), the linker will connect the use of the identifier to its definition.

四、若链接器找不到符号的定义,则报告连接错误。

Otherwise, the linker will issue an error indicating that it couldn’t find a definition for the identifier.

6、 命名空间,即namespace
使用命名空间, 实际是给命名空间内的定义声明一个命名控件作用域
namespace scope.
全局变量可以看看做一个全局 global scope
也可看做global namespace

In C++, any name that is not defined inside a class, function, or a namespace is considered to be part of an implicitly defined namespace called the global namespace (sometimes also called the global scope).

7、预处理, 头文件, 头文件防护
#include #ifdef #ifndef #endif #define
预处理器直接进行文本替换

#define的作用域为定义开始到源文件结束

头文件主要是将声明放在一处地方, 以便于多个源文件能够同时引用到头文件的内容

Best practice
//目前为止,不要把函数和变量定义放在头文件中
Do not put function and variable definitions in your header files (for now).

Defining either of these in a header file will likely result in a violation of the one-definition rule (ODR) if that header is then #included into more than one source (.cpp) file.

Author’s note
//后续将遇到一些定义可以安全地在头文件中定义, 比如类定义,模版定义, 内联函数, 内联变量。
In future lessons, we will encounter additional kinds of definitions that can be safely defined in header files (because they are exempt from the ODR). This includes definitions for inline functions, inline variables, types, and templates. We’ll discuss this further when we introduce each of these.

Best practice

Source files should #include their paired header file (if one exists).

@2.11 this comment

//不要include .cpp文件
Do not #include .cpp files

Although the preprocessor will happily do so, you should generally not #include .cpp files. These should be added to your project and compiled.

There are number of reasons for this:

Doing so can cause naming collisions between source files.
In a large project it can be hard to avoid one definition rules (ODR) issues.
Any change to such a .cpp file will cause both the .cpp file and any other .cpp file that includes it to recompile, which can take a long time. Headers tend to change less often than source files.
It is non-conventional to do so.

Best practice

Avoid #including .cpp files.

//尖括号与 双引号区别

Angled brackets vs double quotes

//尖括号从系统或编译器的 include directory去找头文件, 不会到当前源文件的目录去找。 而"" 先在源文件所在目录寻找头文件,之后再去include directory 中寻找头文件

//建议, 系统库或已安装的第三方库 通过<> , 用户自定义的头文件 使用 “”
You’re probably curious why we use angled brackets for iostream, and double quotes for add.h. It’s possible that a header file with the same filename might exist in multiple directories. Our use of angled brackets vs double quotes helps give the preprocessor a clue as to where it should look for header files.

When we use angled brackets, we’re telling the preprocessor that this is a header file we didn’t write ourselves. The preprocessor will search for the header only in the directories specified by the include directories. The include directories are configured as part of your project/IDE settings/compiler settings, and typically default to the directories containing the header files that come with your compiler and/or OS. The preprocessor will not search for the header file in your project’s source code directory.

When we use double-quotes, we’re telling the preprocessor that this is a header file that we wrote. The preprocessor will first search for the header file in the current directory. If it can’t find a matching header there, it will then search the include directories.

Rule

Use double quotes to include header files that you’ve written or are expected to be found in the current directory. Use angled brackets to include headers that come with your compiler, OS, or third-party libraries you’ve installed elsewhere on your system.

头文件引用问题,

Including header files from other directories

Another common question involves how to include header files from other directories.

一种方法是使用相对路径的方式

One (bad) way to do this is to include a relative path to the header file you want to include as part of the #include line. For example:

#include "headers/myHeader.h"
#include "../moreHeaders/myOtherHeader.h"

While this will compile (assuming the files exist in those relative directories), the downside of this approach is that it requires you to reflect your directory structure in your code. If you ever update your directory structure, your code won’t work anymore.

//更好的方式是将一堆头文件放入一个include 目录

A better method is to tell your compiler or IDE that you have a bunch of header files in some other location, so that it will look there when it can’t find them in the current directory. This can generally be done by setting an include path or search directory in your IDE project settings.

//头文件包含顺序问题, 顺序改变程序可能编译错误
The #include order of header files

If your header files are written properly and #include everything they need, the order of inclusion shouldn’t matter.

Now consider the following scenario: let’s say header A needs declarations from header B, but forgets to include it. In our code file, if we include header B before header A, our code will still compile! This is because the compiler will compile all the declarations from B before it compiles the code from A that depends on those declarations.

However, if we include header A first, then the compiler will complain because the code from A will be compiled before the compiler has seen the declarations from B. This is actually preferable, because the error has been surfaced, and we can then fix it.

//最佳实践
Best practice
1、对应头文件
2、工程其他头文件
3、三方库文件
4、标准库文件
5、按字母顺序排序

To maximize the chance that missing includes will be flagged by compiler, order your #includes as follows:

The paired header file
Other headers from your project
3rd party library headers
Standard library headers
The headers for each grouping should be sorted alphabetically (unless the documentation for a 3rd party library instructs you to do otherwise).

That way, if one of your user-defined headers is missing an #include for a 3rd party library or standard library header, it’s more likely to cause a compile error so you can fix it.

Header file best practices

Here are a few more recommendations for creating and using header files.

总是包含头文件防护

Always include header guards (we’ll cover these next lesson).

头文件不要定义函数或变量 (目前)

Do not define variables and functions in header files (for now).

相同名称的头文件与源文件

Give a header file the same name as the source file it’s associated with (e.g. grades.h is paired with grades.cpp).

头文件的工作比较独立

Each header file should have a specific job, and be as independent as possible. For example, you might put all your declarations related to functionality A in A.h and all your declarations related to functionality B in B.h. That way if you only care about A later, you can just include A.h and not get any of the stuff related to B.

小心显示包含需要使用到的文件

Be mindful of which headers you need to explicitly include for the functionality that you are using in your code files, to avoid inadvertent transitive includes.

包含需要的头文件

A header file should #include any other headers containing functionality it needs. Such a header should compile successfully when #included into a .cpp file by itself.
Only #include what you need (don’t include everything just because you can).

不要include .cpp files
Do not #include .cpp files.

在头文件添加文件功能和如何使用, 源文件具体描述功能是如何起作用的
Prefer putting documentation on what something does or how to use it in the header. It’s more likely to be seen there. Documentation describing how something works should remain in the source files.

头文件防护主要防止单一源文件多次包含一个头文件的问题

Header guards are designed to ensure that the contents of a given header file are not copied more than once into any single file, in order to prevent duplicate definitions.

Duplicate declarations are fine – but even if your header file is composed of all declarations (no definitions) it’s still a best practice to include header guards.

不同源文件仍然可以引入同一头文件, 因为#define的作用域为 定义开始到当前源文件结束处。

Header guards do not prevent a header from being included once into different code files


优化可维护性, 而不是性能。

Optimize for maintainability, not performance. There is a famous quote (by Donald Knuth) that says “premature optimization is the root of all evil”. New programmers often spend far too much time thinking about how to micro-optimize their code (e.g. trying to figure out which of 2 statements is faster). This rarely matters. Most performance benefits come from good program structure, using the right tools and capabilities for the problem at hand, and following best practices. Additional time should be used to improve the maintainability of your code. Find redundancy and remove it. Split up long functions into shorter ones. Replace awkward or hard to use code with something better. The end result will be code that is easier to improve and optimize later (after you’ve determined where optimization is actually needed) and fewer bugs. We offer some additional suggestions in lesson 3.10 – Finding issues before they become problems

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值