Qida's Blog

纸上得来终觉浅,绝知此事要躬行。

C 语言

C++ integrates the operators new and delete for allocating dynamic memory. But these were not available in the C language; instead, it used a library solution, with the functions malloc, calloc, realloc and free, defined in the header <cstdlib> (known as <stdlib.h> in C). The functions are also available in C++ and can also be used to allocate and deallocate dynamic memory.

Note, though, that the memory blocks allocated by these functions are not necessarily compatible with those returned by new, so they should not be mixed; each one should be handled with its own set of functions or operators.

动态内存分配 malloc, calloc, realloc

https://www.runoob.com/cprogramming/c-memory-management.html

动态内存回收 free

https://www.runoob.com/cprogramming/c-memory-management.html

C++ 语言

动态内存分配 new

在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内存(memory heap),这会返回所分配的空间地址

动态内存分配使用 new 运算符,后面跟上一个数据类型,语法如下:

1
2
3
4
5
6
// allocate memory to contain one single element of specified type
pointer = new type

// allocate a block (an array) of elements of specified type, where `number_of_elements` is an integer value representing the amount of these.
// it returns a pointer to the beginning of the new block of memory allocated.
pointer = new type [number_of_elements]

例如:

1
2
int * bar = new int(5); // int bar = 5
int * foo = new int[5]; // int foo[5]

In this case, the system dynamically allocates space for five elements of type int and returns a pointer to the first element of the sequence, which is assigned to foo (a pointer). Therefore, foo now points to a valid block of memory with space for five elements of type int.

Here, foo is a pointer, and thus, the first element pointed to by foo can be accessed either with the expression foo[0] or the expression *foo (both are equivalent). The second element can be accessed either with foo[1] or *(foo+1), and so on…

由于使用动态内存分配机制,因此 number_of_elements 可以是一个变量,变量值在运行时才决定,例如:p = new int[i];


声明普通数组与使用 new 分配动态内存的区别:

There is a substantial difference between declaring a normal array and allocating dynamic memory for a block of memory using new. The most important difference is that the size of a regular array needs to be a constant expression, and thus its size has to be determined at the moment of designing the program, before it is run, whereas the dynamic memory allocation performed by new allows to assign memory during runtime using any variable value as size.


C++ 提供了两种标准机制来检查堆内存分配是否成功:

The dynamic memory requested by our program is allocated by the system from the memory heap. However, computer memory is a limited resource, and it can be exhausted. Therefore, there are no guarantees that all requests to allocate memory using operator new are going to be granted by the system.

C++ provides two standard mechanisms to check if the allocation was successful:

机制一:异常机制

One is by handling exceptions. Using this method, an exception of type bad_alloc is thrown when the allocation fails. If this exception is thrown and it is not handled by a specific handler, the program execution is terminated.

1
foo = new int [5];  // if allocation fails, an exception is thrown

机制二:返回空指针

The other method is known as nothrow, and what happens when it is used is that when a memory allocation fails, instead of throwing a bad_alloc exception or terminating the program, the pointer returned by new is a null pointer, and the program continues its execution normally.

This method can be specified by using a special object called nothrow, declared in header <new>, as argument for new:

1
foo = new (nothrow) int [5];

In this case, if the allocation of this block of memory fails, the failure can be detected by checking if foo is a null pointer:

1
2
3
4
5
int * foo;
foo = new (nothrow) int [5];
if (foo == nullptr) {
// error assigning memory. Take measures.
}

This nothrow method is likely to produce less efficient code than exceptions, since it implies explicitly checking the pointer value returned after each and every allocation. Therefore, the exception mechanism is generally preferred, at least for critical allocations. But nothrow mechanism is more simplicity.

It is considered good practice for programs to always be able to handle failures to allocate memory, either by checking the pointer value (if nothrow) or by catching the proper exception.

动态内存回收 delete

如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的堆内存(memory heap),以便该内存可再次用于其它动态内存分配。语法如下:

1
2
3
4
5
// releases the memory of a single element allocated using new
delete bar;

// releases the memory allocated for arrays of elements using new and a size in brackets ([])
delete [] foo; // 不管所删除数组的维数多少,指针名前只用一对方括号 []

参考

http://www.cplusplus.com/doc/tutorial/dynamic/

Input/Output library

下图是 C++ 提供的输入/输出库,其中:

  • <xxx> 表示:头文件
  • 白框表示:类(Classes)
  • 黑框表示:对象(Objects)

Input/Output library

The iostream library is an object-oriented library that provides input and output functionality using streams.

A stream is an abstraction that represents a device on which input and ouput operations are performed. A stream can basically be represented as a source or destination of characters of indefinite length.

Streams are generally associated to a physical source or destination of characters, like a disk file, the keyboard, or the console, so the characters gotten or written to/from our abstraction called stream are physically input/output to the physical device. For example, file streams are C++ objects to manipulate and interact with files; Once a file stream is used to open a file, any input or output operation performed on that stream is physically reflected in the file.

To operate with streams, C++ provides the standard iostream library, which contains the following elements:

Elements of the iostream library

Basic class templates

The base of the iostream library is the hierarchy of class templates. The class templates provide most of the functionality of the library in a type-independent fashion.

Class template instantiations

The library incorporates two standard sets of instantiations of the entire iostream class template hierarchy:

  • The narrow-oriented (char type) instantiation is probably the better known part of the iostream library. Classes like ios, istream and ofstream are narrow-oriented. The diagram on top of this page shows the names and relationships of narrow-oriented classes.

  • The classes of the wide-oriented (wchar_t) instatiation follow the same naming conventions as the narrow-oriented instantiation but with the name of each class and object prefixed with a w character, forming wios, wistream and wofstream, as an example.

Objects

As part of the iostream library, the header file <iostream> declares certain objects that are used to perform input and output operations on the standard input and output.

They are divided in two sets:

  • narrow-oriented objects: cin, cout, cerr and clog
  • wide-oriented objects: wcin, wcout, wcerr and wclog

Manipulators

Manipulators are global functions designed to be used together with insertion (<<) and extraction (>>) operators performed on iostream stream objects. They generally modify properties and formatting settings of the streams.

常用头文件

下列这些头文件在 C++ 编程中很常用,下面分别介绍:

头文件 函数和描述
<iostream> 该文件定义了 cincoutcerrclog 对象,用于输入输出。
<iomanip> 该文件通过所谓的参数化的流操纵器(比如 setwsetfillsetprecision),来声明对执行标准化 I/O 有用的服务。
<fstream> 该文件定义了 ifstreamofstreamfstream 对象,用于文件读写。

<iostream>

下图摘录了 iostream 类的继承关系及成员函数,如下:

iostream classes

http://www.cplusplus.com/doc/tutorial/basic_io/

http://www.cplusplus.com/reference/iolibrary/

output stream

输出流与流插入运算符 << 配合使用。<iostream> 提供了下列三种输出流对象:

  • cout 标准输出流(默认设备是显示器屏幕)
  • cerr 无缓冲标准错误输出流(默认设备是显示器屏幕)
  • clog 有缓冲标准错误输出流(默认设备是打印机)

此外,<iostream> 还提供了一个常用的操纵符:

  • endl Insert newline and flush

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一些头文件,这些头文件包含了程序中必需的或有用的信息。
#include <iostream>
// 告诉编译器使用 std 命名空间。命名空间是 C++ 中一个相对新的概念。使用该命名空间之后,std::cout 可以简写为:cout
using namespace std;

// 主函数,程序从这里开始执行。
int main()
{
// 在屏幕上输出 "Hello World"
cout << "hello world" << std::endl;

// 终止 main() 函数,并向调用进程返回值 0。
return 0;
}

// 使用 g++ 编译器,编译 cpp 源文件为可执行文件,并执行之
// cd "/Users/wuqd/Documents/workspace/cpp/" && g++ HelloWorld.cpp -o HelloWorld && "/Users/wuqd/Documents/workspace/cpp/"HelloWorld

运行结果:hello world

input stream

输入流与流提取运算符 >> 配合使用。<iostream> 提供了下列一种输入流对象:

  • cin 标准输入流(默认设备是键盘)

例子:

1
2
3
4
5
cin >> name;
cin >> age;

// 流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:
cin >> name >> age;

<iomanip> 流操纵器

常用流操纵器函数如下:

函数 作用
setw Set field width
setfill Set fill character
setprecision Set decimal precision

setw 函数用于设置字段的宽度,只对紧接着的输出产生作用。当后面紧跟着的输出字段长度小于 n 的时候,在该字段前面默认用空格补齐,当输出字段长度大于 n 时,全部整体输出。如下:

setw

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <iomanip>
using namespace std;

/*
* 测试 I/O
*/
int main()
{
double pi = 3.1415926;

// 默认右补位,可以使用 setf() 进行左补位
cout.setf(ios::left);

// setprecision() 包含小数点
cout << setfill('*') << setw(5) << setprecision(3) << pi << endl;

return 0;
}

运行结果:3.14*

参考:

https://www.runoob.com/w3cnote/cpp-func-setw.html

<fstream> 文件读写

<fstream> 定义了下面三个对象,用于文件读写:

数据类型 描述
ofstream 该数据类型表示输出文件流,用于创建文件并向文件写入信息。
ifstream 该数据类型表示输入文件流,用于从文件读取信息。
fstream 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <fstream>
using namespace std;

void output() {
ofstream myfile;
// 打开文件
myfile.open("/Users/wuqd/Desktop/test", ios::app); // ios::app 表示追加模式。所有写入都追加到文件末尾。
// 写入文件
myfile << "helloworld" << endl;
// 关闭文件
myfile.close();
}

void input() {
ifstream in;
// 打开文件
in.open("/Users/wuqd/Desktop/test");
char data[100];
// 读取文件
while (in >> data) {
// 输出到屏幕
cout << data << endl;
}
// 关闭文件
in.close();
}

int main() {
output();
input();
return 0;
}

参考:

https://www.cplusplus.com/doc/oldtutorial/files/

https://www.runoob.com/cplusplus/cpp-files-streams.html

继承

C++ 继承的语法如下,支持单继承和多继承:

1
2
3
4
5
// Single inheritance 单继承
class derived_class: access_specifier base_class

// Multiple inheritance 多重继承,各个基类之间用逗号分隔
class derived_class: access_specifier base_class_1, access_specifier base_class_2, ...

继承类型

继承类型通过访问修饰符 access_specifier 来指定:

继承类型 基类的 public 成员 基类的 protected 成员 基类的 private 成员
公有继承(public 派生类的 public 成员 派生类的 protected 成员 无法继承
保护继承(protected 派生类的 protected 成员 派生类的 protected 成员 无法继承
私有继承(private 派生类的 private 成员 派生类的 private 成员 无法继承

通常使用 public 继承,几乎不使用 protectedprivate 继承。

In principle, a publicly derived class inherits access to every member of a base class except:

  • its constructors and its destructor
  • its assignment operator members (operator=)
  • its friends
  • its private members

继承的访问控制属性

Access public protected private
members of the same class yes yes yes
members of derived class yes yes no
not members yes no no

构造、析构函数执行顺序

Even though access to the constructors and destructor of the base class is not inherited, they are automatically called by the constructors and destructor of the derived class.

Unless otherwise specified, the constructors of a derived class calls the default constructor of its base classes (i.e., the constructor taking no arguments).

继承后,执行顺序如下:

  • 构造函数:先父后子
  • 析构函数:先子后父

子类调用父类方法

BaseClass::Function()

多态

One of the key features of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature.

类继承的关键特性之一,就是指向派生类的指针与指向其基类的指针在类型上兼容。 多态是利用这一简单但强大而通用的功能的艺术。

下面使用指针来演示多态这一特性。

UML 类图如下:

UML 类图

类声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// pointers to base class
#include <iostream>
using namespace std;

class Shape {
protected:
int width, height;
public:
Shape(int width, int height) {
this->width = width;
this->height = height;
}
virtual int area() { return 0; }
};

class Rectangle : public Shape {
public:
Rectangle(int x, int y) : Shape(x, y) {};
int area() { return width * height; }
};

class Triangle : public Shape {
public:
Triangle(int x, int y) : Shape(x, y) {};
int area() { return width * height / 2; }
};

使用方式一:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
Rectangle rect(2, 3);
Triangle trgl(2, 3);

Shape * p1 = &rect;
Shape * p2 = &trgl;

cout << "rectangle area is " << p1->area() << endl; // rectangle area is 6
cout << "triangle area is " << p2->area() << endl; // triangle area is 3

return 0;
}

使用方式二,参考:Dynamic memory

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {

Shape * p1 = new Rectangle(2, 3);
Shape * p2 = new Triangle(2, 3);

cout << "rectangle area is " << p1->area() << endl; // rectangle area is 6
cout << "triangle area is " << p2->area() << endl; // triangle area is 3

delete p3;
delete p4;

return 0;
}

描述如下:

Function main declares two pointers to Shape (named p1 and p2). These are assigned the addresses of rect and trgl, respectively, which are objects of type Rectangle and Triangle. Such assignments are valid, since both Rectangle and Triangle are classes derived from Shape.

Dereferencing p1 and p2 (with p1-> and p2->) is valid and allows us to access the members of their pointed objects. For example, the following two statements would be equivalent in the previous example:

1
2
rect.area();
p1->area();

虚函数(virtual)

虚函数是在基类中使用 virtual 关键字声明的函数,是可以在派生类中重定义的成员函数。虚函数用于实现运行时多态

1
2
3
4
5
6
7
8
9
10
11
12
13
+--------------------+
| Base Class |
| virtual function |
+---------^----------+
|
|class inheritance
|
+---------+----------+
| Derived class |
| redefined function|
+--------------------+

运行时多态的实现手段:虚函数 + 继承 + 函数重定义

派生类也可以重定义基类的非虚函数,但无法通过基类的引用来访问派生类的该函数。即:如果移除上述基类中 area 函数声明的 virtual 关键字,下面的函数调用将返回 0,因为实际调用的是基类的版本:

1
2
cout << "rectangle area is " << p1->area() << endl;  // rectangle area is 0
cout << "triangle area is " << p2->area() << endl; // triangle area is 0

Therefore, essentially, what the virtual keyword does is to allow a member of a derived class with the same name as one in the base class to be appropriately called from a pointer, and more precisely when the type of the pointer is a pointer to the base class that is pointing to an object of the derived class, as in the above example.

A class that declares or inherits a virtual function is called a polymorphic class.

注意,尽管成员之一是 virtual 的,但 Sharp 仍然是一个常规类,可以实例化对象。

纯虚函数(抽象类)

Classes that contain at least one pure virtual function are known as abstract base classes. The syntax of pure virtual function is to replace their definition by =0 (an equal sign and a zero):

1
2
3
4
5
6
7
// abstract class CPolygon
class Shape {
protected:
int width, height;
public:
virtual int area () = 0;
};

Abstract base classes cannot be used to instantiate objects:

1
2
3
// 不允许使用抽象类类型 "Shape" 的对象: -- 函数 "Shape::area" 是纯虚函数
// variable type 'Shape' is an abstract class
Shape shape;

But an abstract base class is not totally useless. It can be used to create pointers to it, and take advantage of all its polymorphic abilities.

1
2
3
// the following pointer declarations would be valid
Shape * p1 = &rect;
Shape * p2 = &trgl;

Virtual members and abstract classes grant C++ polymorphic characteristics, most useful for object-oriented projects.

C++ 接口是使用抽象类来实现的。

参考

http://www.cplusplus.com/doc/tutorial/inheritance/

http://www.cplusplus.com/doc/tutorial/polymorphism/

类的声明:

1
2
3
4
5
6
7
class class_name {
access_specifier_1:
member1;
access_specifier_2:
member2;
...
} object_names;

访问修饰符

  • private members of a class are accessible only from within other members of the same class (or from their friend). By default, all members of a class have private access for all its members.
  • protected members are accessible from other members of the same class (or from their friend), but also from members of their derived classes.
  • Finally, public members are accessible from anywhere where the object is visible.

this 指针

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

静态成员(static)

static 关键字用于修饰静态成员变量或函数。限制如下:

  • 无法访问类的非静态成员变量或函数;
  • 无法使用 this 指针。

静态成员的引用方式:Runoob:runoob_age

成员函数

有两种方式定义类的成员函数:

  • 内联成员函数(inline member function)
  • 普通成员函数(not-inline member function)

两种方式并不会导致行为上的差异,而只会导致可能的编译器优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// classes example
#include <iostream>
using namespace std;

class Rectangle {
int width, height;
public:
// declaration of a member function within the class
void set_values (int, int);
// defining a member function completely within the class definition
int area() {return width*height;}
};

// definition of a member function of a class outside the class itself.
// The scope operator (::) specifies the class to which the member being defined belongs, granting exactly the same scope properties as if this function definition was directly included within the class definition.
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
};

int main () {
Rectangle rect;

// public members of object can be accessed by dot operator (.)
rect.set_values (3,4);
cout << "area: " << rect.area() << endl;
return 0;
}

常成员函数

To specify that a member is a const member, the const keyword shall follow the function prototype, after the closing parenthesis for its parameters:

1
int get() const {return x;}        // const member function

构造函数

类的构造函数是类的一种特殊的成员函数,构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。它会在每次创建类的新对象时执行:

1
2
3
4
// 调用有参构造函数
Rectangle rect(1, 2); // Object is being created, width=1, height=2
// 调用默认构造函数
Rectangle rectb; // Object is being created

构造函数重载

Overloading constructors

Like any other function, a constructor can also be overloaded with different versions taking different parameters: with a different number of parameters and/or parameters of different types. The compiler will automatically call the one whose parameters match the arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Rectangle {
int width, height;
public:
// 声明一个无参构造函数
Rectangle();
// 声明一个有参构造函数
Rectangle(int, int);
};

// 定义一个无参构造函数
Rectangle::Rectangle() {
cout << "Object is being created" << endl;
}

// 定义一个有参构造函数
Rectangle::Rectangle(int width, int height) {
this->width = width;
this->height = height;
cout << "Object is being created, width=" << this->width << ", height=" << this->height << endl;
};

This example introduces a special kind constructor: the default constructor. The default constructor is the constructor that takes no parameters, and it is special because it is called when an object is declared but is not initialized with any arguments. In the example above, the default constructor is called for rectb. Note how rectb is not even constructed with an empty set of parentheses - in fact, empty parentheses cannot be used to call the default constructor:

1
2
Rectangle rectb;   // ok, default constructor called
Rectangle rectc(); // oops, default constructor NOT called, empty parentheses interpreted as a function declaration

This is because the empty set of parentheses would make of rectc a function declaration instead of an object declaration: It would be a function that takes no arguments and returns a value of type Rectangle.

在构造函数中初始化成员变量

使用构造函数初始化其他成员变量时,有下面两种方式:

Member initialization in constructors

When a constructor is used to initialize other members, these other members can be initialized directly, without resorting to statements in its body. This is done by inserting, before the constructor’s body, a colon (:) and a list of initializations for class members. For example, consider a class with the following declaration:

1
2
3
4
5
6
class Rectangle {
int width, height;
public:
Rectangle(int, int);
int area() {return width*height;}
};

The constructor for this class could be defined, as usual, as:

1
Rectangle::Rectangle(int x, int y) { width=x; height=y; }

But it could also be defined using member initialization as:

1
Rectangle::Rectangle(int x, int y) : width(x), height(y) { }

Or even:

1
Rectangle::Rectangle(int x, int y) : Shape(x, y) { }

Note how in this last case, the constructor does nothing else than initialize its members, hence it has an empty function body.

析构函数(~)

类的析构函数是类的一种特殊的成员函数,它会在每次删除对象时执行,有助于在跳出程序前释放资源(比如关闭文件、释放内存等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Rectangle {
int width, height;
public:
// 构造函数
Rectangle();
// 析构函数
~Rectangle();
};

Rectangle::Rectangle(){
cout << "Object is being created" << endl;
};

Rectangle::~Rectangle(){
cout << "Object is being deleted" << endl;
};

析构函数要点:

  • 析构函数名称与类的名称完全相同,前缀使用关键字 ~
  • 一个类中只能声明一个析构函数(destructor cannot be redeclared);
  • 析构函数无参数(destructor cannot have any parameters);
  • 析构函数无返回值(destructor cannot have a return type);
  • 不可重载。

友元函数(friend)

类的友元函数(friend 关键字),有权访问类的所有私有(private)和保护(protected)成员变量。尽管友元函数在类中声明,但是友元函数并不是类的成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Rectangle {
int width, height;
public:
friend int getWidth(Rectangle);
friend int getHeight(Rectangle rect) {
return rect.height;
}
};

int getWidth(Rectangle rect) {
return rect.width;
}

int main () {
Rectangle rect1(1, 2);

cout << "width: " << getWidth(rect1) << " height: " << getHeight(rect1) << endl; // width: 1 height: 2
return 0;
}

友元函数破坏了类的封装性,实践中不建议使用。

重载(overload)

函数重载

可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。

运算符重载

一、支持重载的运算符:

C++ allows most operators to be overloaded so that their behavior can be defined for just about any type, including classes. Here is a list of all the operators that can be overloaded:

1
2
3
4
+    -    *    /    =    <    >    +=   -=   *=   /=   <<   >>
<<= >>= == != <= >= ++ -- % & ^ ! |
~ &= ^= |= && || %= [] () , ->* -> new
delete new[] delete[]

Operators are overloaded by means of operator functions, which are regular functions with special names: their name begins by the operator keyword followed by the operator sign that is overloaded. The syntax is:

1
type operator sign (parameters) { /*... body ...*/ }

二、重载运算符的不同形式:

There is a table with a summary of the parameters needed for each of the different operators than can be overloaded (please, replace @ by the operator in each case):

Expression Operator Member function Non-member function
@a + - * & ! ~ ++ -- A::operator@() operator@(A)
a@ ++ -- A::operator@(int) operator@(A,int)
a@b + - * / % ^ & | < > == != <= >= << >> && || , A::operator@(B) operator@(A,B)
a@b = += -= *= /= %= ^= &= |= <<= >>= [] A::operator@(B) -
a(b,c...) () A::operator()(B,C...) -
a->b -> A::operator->() -
(TYPE) a TYPE A::operator TYPE() -

Where a is an object of class A, b is an object of class B and c is an object of class C. TYPE is just any type (that operators overloads the conversion to type TYPE).

Notice that some operators may be overloaded in two forms: either as a member function or as a non-member function.

三、例子:

For example, cartesian vectors are sets of two coordinates: x and y. The addition operation of two cartesian vectors is defined as the addition both x coordinates together, and both y coordinates together. For example, adding the cartesian vectors (3,1) and (1,2) together would result in (3+1,1+2) = (4,3). This could be implemented in C++ with the following code:

Overloading operators

The function operator+ of class CVector overloads the addition operator (+) for that type. Once declared, this function can be called either implicitly using the operator, or explicitly using its functional name:

1
2
3
4
5
// called either implicitly using the operator
c = a + b;

// or explicitly using its functional name
c = a.operator+ (b);

Both expressions are equivalent.

四、注意点:

Attention

The operator overloads are just regular functions which can have any behavior; there is actually no requirement that the operation performed by that overload bears a relation to the mathematical or usual meaning of the operator, although it is strongly recommended. For example, a class that overloads operator+ to actually subtract or that overloads operator== to fill the object with zeros, is perfectly valid, although using such a class could be challenging.

函数重定义(redefine)

即 Java 语言中的方法重写(rewrite)。

类指针

Objects can also be pointed to by pointers: Once declared, a class becomes a valid type, so it can be used as the type pointed to by a pointer. For example:

1
2
// a pointer to an object of class Rectangle.
Rectangle * prect;

Similarly as with plain data structures, the members of an object can be accessed directly from a pointer by using the arrow operator (->). Here is an example with some possible combinations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pointer to classes example
#include <iostream>
using namespace std;

class Rectangle {
int width, height;
public:
Rectangle(int x, int y) : width(x), height(y) {}
int area(void) { return width * height; }
};

int main() {
Rectangle obj(3, 4);
Rectangle * foo, * bar; // 类指针(Pointers to classes)
foo = &obj;
bar = new Rectangle (5, 6); // 参考:http://www.cplusplus.com/doc/tutorial/dynamic/
cout << "obj's area: " << obj.area() << '\n';
cout << "*foo's area: " << foo->area() << '\n';
cout << "*foo's area: " << (*foo).area() << '\n';
cout << "*bar's area: " << bar->area() << '\n';
cout << "*bar's area: " << (*bar).area() << '\n';
delete bar;
return 0;
}

This example makes use of several operators to operate on objects and pointers (operators *, &, ., ->). They can be interpreted as:

expression can be read as
*x pointed to by x
&x address of x
x.y member y of object x
x->y member y of object pointed to by x
(*x).y member y of object pointed to by x (equivalent to the previous one)

模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

函数模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// function templates
#include <iostream>
using namespace std;

template <typename T>
T getmax (T a, T b)
{
T retval;
retval = a > b ? a : b;
return retval;
}

int main () {
int maxInt = getmax(100, 75);
cout << maxInt << endl; // 100

double maxDouble = getmax(3.3, 2.18);
cout << maxDouble << endl; // 3.3

// no matching function for call to 'getmax'
// char maxChar = getmax('a', 1.99);
// cout << maxChar << endl;

return 0;
}

类模板

Just like we can create function templates, we can also create class templates, allowing classes to have members that use template parameters as types. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// class templates
#include <iostream>
using namespace std;

template <class T>
class MyPair {
T a, b;
public:
MyPair (T first, T second)
{a=first; b=second;}
T getmax ();
};

// In case that a member function is defined outside the defintion of the class template, it shall be preceded with the template <...> prefix
template <class T>
T MyPair<T>::getmax ()
{
T retval;
retval = a > b ? a : b;
return retval;
}

int main () {
MyPair<int> myobject(100, 75);
cout << myobject.getmax() << endl; // 100

MyPair<double> myfloats(3.3, 2.18);
cout << myfloats.getmax() << endl; // 3.3

// implicit conversion from 'double' to 'char' changes value from 1.99 to 1
// MyPair<char> mychars('a', 1.99);
// cout << mychars.getmax() << endl;

return 0;
}

Notice the syntax of the definition of member function getmax:

1
2
template <class T>
T mypair<T>::getmax ()

There are three T‘s in this declaration: The first one is the template parameter. The second T refers to the type returned by the function. And the third T (the one between angle brackets) is also a requirement: It specifies that this function’s template parameter is also the class template parameter.

模板类

模板类是类模板实例化后的一个产物。

It is possible to define a different implementation for a template when a specific type is passed as template argument. This is called a template specialization.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// template specialization
#include <iostream>
using namespace std;

// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};

// class template specialization:
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};

int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl; // 8
cout << mychar.uppercase() << endl; // J
return 0;
}

This is the syntax used for the class template specialization:

1
2
template <>
class mycontainer <char> { ... };

First of all, notice that we precede the class name with template<> , including an empty parameter list. This is because all types are known and no template arguments are required for this specialization, but still, it is the specialization of a class template, and thus it requires to be noted as such.

But more important than this prefix, is the <char> specialization parameter after the class template name. This specialization parameter itself identifies the type for which the template class is being specialized (char). Notice the differences between the generic class template and the specialization:

1
2
template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };

The first line is the generic template, and the second one is the specialization.

When we declare specializations for a template class, we must also define all its members, even those identical to the generic template class, because there is no “inheritance” of members from the generic template to the specialization.

参考

http://www.cplusplus.com/doc/tutorial/classes/

http://www.cplusplus.com/doc/tutorial/templates/

http://www.cplusplus.com/doc/tutorial/classes2/

https://www.cplusplus.com/doc/oldtutorial/templates/

https://www.runoob.com/cplusplus/cpp-classes-objects.html

https://www.runoob.com/cplusplus/cpp-templates.html

A type alias is a different name by which a type can be identified. In C++, any valid type can be aliased so that it can be referred to with a different identifier.

在 C++ 中,有两种创建类型别名的语法:

  1. 从 C 语言继承而来,使用 typedef 关键字:

    1
    typedef existing_type new_type_name ;
  2. 由 C++ 语言引入,使用 using 关键字:

    1
    using new_type_name = existing_type ;

existing_type 可以是任何类型,无论是基本类型还是复合类型:

例子一:

typedef using
typedef char C; using C = char;
typedef unsigned int WORD; using WORD = unsigned int;
typedef char * pChar; using pChar = char *;
typedef char field [50]; using field = char [50];

例子二,下面两种定义结构体类型的方式是等价的:

1
2
3
4
5
6
7
8
9
struct product {
int weight;
double price;
};

typedef struct {
int weight;
double price;
} product;

new_type_name 作为该类型的别名,用法如下:

1
2
3
4
C mychar, anotherchar, *ptc1;
WORD myword;
pChar ptc2;
field name;

一旦定义了这些别名,就可以像其它有效类型一样,在任何声明中使用。尤其常见于与结构体搭配使用。

参考

https://www.cplusplus.com/doc/tutorial/other_data_types/

声明语法

1
2
3
4
5
6
7
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;

定义用法

结构体对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// declare three objects (variables) of this type (product): apple, banana, and melon.
struct product {
int weight;
double price;
} apple, banana, melon;

// struct requires either a type_name or at least one name in object_names, but not necessarily both.
struct {
int weight;
double price;
} apple, banana, melon;

// declare three objects (variables) of this type (product): apple, banana, and melon.
product apple, banana, melon;

结构体数组

1
2
// because structures are types, they can also be used as the type of arrays.
product banana[3];

结构体指针

1
product * p = &apple;

创建结构体指针之后,可以使用以下运算符访问其成员变量:

Operator Expression What is evaluated Equivalent
dot operator (.) a.b Member b of object a
arrow operator (->)
(dereference operator)
a->b Member b of object pointed to by a (*a).b

例子:

1
2
3
4
5
apple.weight;  // 3
// The arrow operator (->) is a dereference operator that is used exclusively with pointers to objects that have members. This operator serves to access the member of an object directly from its address.
p->weight; // 3
// equivalent to:
(*p).weight; // 3

例子 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

struct product {
int weight;
double price;
};

void test() {
product apple;
apple.weight = 3;
apple.price = 4;
product * p_apple = &apple;

cout << "weight: " << (*p_apple).weight << endl; // weight: 3
cout << "weight: " << p_apple->weight << endl; // weight: 3

cout << "address of apple: " << p_apple << endl; // address of apple: 0x7ffee256b5b0
cout << "address of weight: " << &p_apple->weight << endl; // address of weight: 0x7ffee256b5b0
cout << "address of price: " << &p_apple->price << endl; // address of price: 0x7ffee256b5b8
cout << "size of weight: " << sizeof(p_apple->weight) << endl; // size of weight: 4
cout << "size of price: " << sizeof(p_apple->price) << endl; // size of price: 8
}

int main() {
test();
return 0;
}

结构体作为函数参数

支持三种方式的传参:

  • 传值
  • 传引用
  • 传址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;

struct product {
int weight;
double price;
} lemon;

// 传值
void show(product prd) {
cout << "weight: " << prd.weight << " price: " << prd.price << endl;
}

// 传引用
void show2(product &prd) {
cout << "weight: " << prd.weight << " price: " << prd.price << endl;
}

// 传址
void show3(product * prd) {
cout << "weight: " << prd->weight << " price: " << prd->price << endl;
}

void test() {
lemon.weight = 1;
lemon.price = 2;
show(lemon); // weight: 1 price: 2
show2(lemon); // weight: 1 price: 2
show3(&lemon); // weight: 1 price: 2
}

int main() {
test();
return 0;
}

参考

http://www.cplusplus.com/doc/tutorial/structures/

重点:区分下述四种形式:

1
2
3
4
5
6
7
// 字符数组(Character sequences)
char str[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};
char str[] = "hello world";
// 字符串指针
char *str = "hello world";
// 字符串类(string)
string str = "hello world";

字符数组

字符数组(Character sequences):http://www.cplusplus.com/doc/tutorial/ntcs/

字符串实际上是使用 null 字符 \0 终止的一维字符数组,如下:

C 字符串

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串实际上是使用 null 字符 \0 终止的一维字符数组
char str[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};
// 依据数组初始化规则,简化如下:
char str2[] = "hello world";
// conversion from string literal to 'char *' is deprecated
char *str3 = "hello world";

// str: hello world size: 12
cout << "str: " << str << " size: " << sizeof(str) << endl;
// str2: hello world size: 12
cout << "str2: " << str2 << " size: " << sizeof(str2) << endl;
// 'sizeof (str3)' will return the size of the pointer, not the array itself
// str3: hello world size: 8
cout << "str3: " << str3 << " size: " << sizeof(str3) << endl;

字符串指针

用字符数组和字符串指针都可实现字符串的存储和运算,但是两者是有区别的:

  • 字符数组是一个数组,每个元素的值都可以改变。
  • 而字符串指针指向的是一个常量字符串,它被存放在程序的静态数据区,一旦定义就不能改变

这是最重要的区别。下面的代码在运行期间将会出错:

1
2
str2[1] = 'a';      // hallo world
*(str3 + 1) = 'a'; // 运行时出错。因为不能改变字符串常量的值

string 字符串类

string 头文件提供了 string 类,参考:http://www.cplusplus.com/reference/string/

cstring 操纵器

cstring 头文件提供了大量的函数,用来操纵 C strings and arrays,参考:http://www.cplusplus.com/reference/cstring/

例如:

  • Copying:
  • Concatenation:
  • Comparison:
  • Searching:
    • strchr Locate first occurrence of character in string
    • strrchr Locate last occurrence of character in string
  • Other:

参考

https://www.cplusplus.com/doc/tutorial/ntcs/

https://www.runoob.com/cplusplus/cpp-strings.html

C++输出char型变量与字符串的地址

https://stackoverflow.com/questions/1524356/c-deprecated-conversion-from-string-constant-to-char

一维数组

一维数组声明:type arrayName[ arraySize ];

1
2
// 声明数组
int array1[5];

一维数组初始化:

1
2
3
4
// 初始化数组
float array1[5] = {1, 2, 3, 4, 5};
// 初始化数组(省略数组大小声明,默认大小为初始化时元素的个数)
float array2[] = {1, 2, 3, 4, 5};

一维数组访问:

1
2
3
4
5
6
// 通过索引逐个访问数组元素,并赋值
array1[0]; // 1
array1[1]; // 2
array1[2]; // 3
array1[3]; // 4
array1[4]; // 5

多维数组

多维数组声明:type name[ size1 ][ size2 ]...[ sizeN ];

1
2
// 声明一个三维 5 . 10 . 4 整型数组
int array1[5][10][4];

二维数组初始化:

1
2
3
4
5
6
7
8
9
// 多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};

// 内部嵌套的括号是可选的,下面的初始化与上面是等同的:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

二维数组访问:

1
2
3
4
5
6
7
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
for (int j = 0; j < sizeof(a[0]) / sizeof(a[0][0]); j++)
{
cout << a[i][j] << endl;
}
}

二维数组大小及长度获取:

1
2
3
4
5
6
7
8
// 数组总大小:48
sizeof(a)
// 数组第一维大小:16
sizeof(a[0])
// 数组第一维长度:3
sizeof(a) / sizeof(a[0])
// 数组第二维长度:4
sizeof(a[0]) / sizeof(a[0][0])

数组名

数组名是指向数组中第一个元素的常指针(常量指针),因此 arrayName 等价于 &arrayName[0],验证如下:

1
2
3
int arrayName[] = {0, 1, 2, 3, 4};
cout << arrayName << endl; // 0x7ffeec364620
cout << &arrayName[0] << endl; // 0x7ffeec364620

由于数组是常指针,因此自身无法执行算术运算:

1
2
3
4
5
6
7
8
// 表达式必须是可修改的左值
// invalid operands to binary expression
arrayName += 1;
arrayName -= 1;
arrayName++;
arrayName--;
++arrayName;
--arrayName;

数组构成

  • 存储一个固定大小相同数据类型元素的顺序集合。
  • 连续的内存地址组成。

验证如下:

1
2
3
for (int i = 0; i < 5; i++) {
cout << &arrayName[i] << endl;
}

输出如下:

1
2
3
4
5
0x7ffeec364620
0x7ffeec364624
0x7ffeec364628
0x7ffeec36462c
0x7ffeec364630

分析上述输出结果,由于 1 个内存单元的大小是 8 bits,即一个字节。而一个 int 类型的变量占用 4 个字节,因此上述 16 进制表示的内存地址的递增步长为 4。

获取数组大小

通过 sizeof 运算符,确认该数组大小为 20 字节:

1
sizeof(arrayName) // 20

获取数组长度

sizeof 运算符用于获取变量的存储大小(即所占内存字节数),由于数组中每个元素的类型都是一样的、所占字节数亦然,因此可以计算如下:

1
sizeof(arrayName) / sizeof(arrayName[0]);  // 5

参考:C 语言数组传入函数获取数组长度的方法

遍历数组元素

使用数组索引访问数组元素:

1
2
3
for (int i = 0; i < 5; i++) {
cout << arrayName[i] << endl; // 1 2 3 4 5
}

指针数组 VS 数组指针

指针数据 VS 数组指针

指针数组

指针数组,表示“存储指针的数组”,即定义一个数组,其每个元素都是指针:

1
int *p1[10];

数组指针

数组指针,表示“指向数组的指针”,即定义一个指针,指向数组:

1
int *p2 = new int[10];

常用于定义函数的形式参数

1
void func(int *p);

优点:数组作为常指针不能执行算术运算,但数组指针可以。下面通过递增指针,以顺序访问数组元素:

1
2
3
4
5
6
7
8
int arrayName[5] = {1, 2, 3, 4, 5};  // &arrayName = 0x7ffee7376620
int *p = arrayName;

for (int i = 0; i < 5; i++)
{
cout << p << endl; // 0x7ffee7376620 0x7ffee7376624 0x7ffee7376628 0x7ffee737662c 0x7ffee7376630
cout << *p++ << endl; // 1 2 3 4 5
}

传递数组给函数

三种方式

有三种方式传递数组给函数。

方式一:形式参数是一个数组指针(指向数组的指针)。

1
2
3
4
void func(int *p)
{
cout << sizeof(p) << endl; // 返回指针长度 8 bytes
}

方式二:形式参数是一个已定义大小的数组。

1
2
3
4
5
void func(int p[5])
{
// sizeof on array function parameter will return size of 'int *' instead of 'int [5]'
cout << sizeof(p) << endl; // 返回指针长度 8 bytes
}

方式三:形式参数是一个未定义大小的数组。

1
2
3
4
5
void func(int p[])
{
// sizeof on array function parameter will return size of 'int *' instead of 'int []'
cout << sizeof(p) << endl; // 返回指针长度 8 bytes
}

注意点

无论使用何种方式,函数内都无法获取数组长度,需要使用单独变量将数组长度作为参数传入。

1
2
3
4
5
6
7
void func(int p[], int length)
{
for (int i = 0; i < 5; i++)
{
cout << p[i] << endl; // 1 2 3 4 5
}
}

参考

http://www.cplusplus.com/doc/tutorial/arrays/

https://www.runoob.com/cplusplus/cpp-arrays.html

引用

引用是一个别名,也就是说,它是某个已存在变量的另一个名字。修改引用等同于修改被引用变量自身。

一个变量可以有多个引用。

声明引用

不存在空引用。声明引用的同时,必须初始化,否则报编译错误如下:

1
int &ref;  // declaration of reference variable 'ref' requires an initializer

引用一旦初始化,就不能再指向另一个变量。修改引用等同于修改被引用变量本身。

指针

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。

声明指针

一元运算符 * 用于声明一个指针变量:

1
2
3
4
5
bool   *bp;    // 声明一个布尔型的空指针
char *ch; // 声明一个字符型的空指针
int *ip; // 声明一个整型的空指针
double *dp; // 声明声明一个 double 型的空指针
float *fp; // 声明一个浮点型的空指针
  • 可以声明空指针。
  • 除了常指针,其它指针可以在任何时间被初始化。
  • 所有指针的值的实际数据类型,不管是布尔型、字符型、整型、浮点型,还是其它的数据类型,都是一样的,其值都是一个代表内存地址十六进制数
  • 不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同,因此执行递增或递减时的步长不同

获取指针大小

操作系统 指针变量的存储大小
32 bits 4 Bytes
64 bits 8 Bytes

本机为 64 位操作系统,验证如下:

1
2
3
4
5
sizeof(bool*)  // 8
sizeof(char*) // 8
sizeof(int*) // 8
sizeof(float*) // 8
sizeof(double*) // 8

指针的算数运算

可以对指针进行四种算术运算:++--+-。运算后,指针保存新的地址。

1
2
3
4
5
6
7
8
9
10
11
int a = 0;
int b = 1;
int * p = &a;

p = &b;
p += 1;
p -= 1;
p++;
p--;
++p;
--p;

重点考点

1
2
3
4
5
6
7
8
// 常指针
int* const p = &a;

// 指向常量的指针
const int * p;

// 指向常量的常指针
const int * const p = &a;

常指针

顾名思义,指针本身是个常量,不能修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
int a = 0;
int b = 1;
int* const p = &a;

// 表达式必须是可修改的左值
// cannot assign to variable 'p' with const-qualified type 'int *const'
p = &b;
p += 1;
p -= 1;
p++;
p--;
++p;
--p;

常见的常指针,例如:

  • 数组名

指向常量的指针

顾名思义,指针指向的是常量,不能修改其值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a = 0;
int b = 1;
const int * p = &a;

// 指针本身可以重新赋值
p = &b;

// 但不能修改指针指向的常量的值,否则报编译错误:
// 表达式必须是可修改的左值
// read-only variable is not assignable
*p += 1;
*p -= 1;
(*p)++;
(*p)--;
++(*p);
--(*p);

指向常量的常指针

结合了上述两种特性。

常见指针

字符串指针

1
2
// 字符串指针(指向一个常量字符串,它被存放在程序的静态数据区,一旦定义就不能改变)
char * str = "hello world";

数组指针

1
2
3
4
5
6
7
8
int arrayName[5] = {1, 2, 3, 4, 5};  // &arrayName = 0x7ffee7376620
int * p = arrayName; // // 数组指针(Pointers to array,即指向数组中第一个元素的地址)

for (int i = 0; i < 5; i++)
{
cout << p << endl; // 0x7ffee7376620 0x7ffee7376624 0x7ffee7376628 0x7ffee737662c 0x7ffee7376630
cout << *p++ << endl; // 1 2 3 4 5
}

结构体指针

1
2
3
4
5
6
7
product apple;
// 结构体指针(Pointers to struct)
product * p = &apple;
// The arrow operator (->) is a dereference operator that is used exclusively with pointers to objects that have members. This operator serves to access the member of an object directly from its address.
p->weight;
// equivalent to:
(*p).weight;

类指针

1
2
3
4
5
6
7
8
9
10
Rectangle rect(3, 4);
// 类指针(Pointers to classes),主要用于多态性
Shape * p = &rect;

// member y of object x
rect.area(); // 12
// member y of object pointed to by x
p->area(); // 12
// equivalent to:
(*p).area(); // 12

引用与指针对比

常见问题,下列代码的区别?

1
2
3
4
*p
&p
*&p
&*p

要解决这个问题,首先需要了解这两个运算符的区别:

在赋值运算符左侧 在赋值运算符右侧
* 表示声明指针 表示取值运算符
& 表示声明引用 表示取址运算符

代码示例如下:

1
2
3
4
5
6
7
int a = 10;
int& b = a;
int* p = &a;

cout << "a = " << a << ", &a = " << &a << ", *&a = " << *&a << endl;
cout << "b = " << b << ", &b = " << &b << ", *&b = " << *&b << endl;
cout << "p = " << p << ", &p = " << &p << ", *p = " << *p << ", &*p = " << &*p << ", *&p = " << *&p << endl

输出结果如下:

1
2
3
a = 10, &a = 0x7ffee483763c, *&a = 10
b = 10, &b = 0x7ffee483763c, *&b = 10
p = 0x7ffee483763c, &p = 0x7ffee4837628, *p = 10, &*p = 0x7ffee483763c, *&p = 0x7ffee483763c

总结如下:

指针变量 结果 描述
p 0x7ffee483763c 返回指针变量 p 保存的地址
*p 10 返回指针变量 p 保存的地址的实际值
&p 0x7ffee4837628 返回指针变量 p 自身的地址
&*p 0x7ffee483763c 返回指针变量 p 保存的地址的实际值的地址
*&p 0x7ffee483763c 返回指针变量 p 自身的地址的实际值

引用与指针的区别,如下图:

引用与指针对比

参考

http://www.cplusplus.com/doc/tutorial/pointers/

Difference between pointer and reference in C ?

C++ 中的参数传递方式:传值、传地址、传引用总结

32/64 位系统支持多大内存?

函数定义

函数定义形式如下:

  • 函数头

    • 返回类型

    • 函数名称

    • 形式参数

      • 无参数函数

      • 有参数函数

        • 参数默认值(必须从右到左赋默认值)

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          // 函数默认值,必须从右到左
          int max2(int num1, int num2 = 100)
          {
          return num1 > num2 ? num1 : num2;
          }

          // 否则报编译错误:missing default argument on parameter 'num2'
          int max3(int num1 = 100, int num2)
          {
          return num1 > num2 ? num1 : num2;
          }
  • 函数体

函数声明

函数必须先声明后使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

// 函数必须先声明后使用
int max1(int num1, int num2);

int main()
{
cout << "result is " << max1(1, 10) << endl; // 否则报错:未定义标识符 use of undeclared identifier 'max1'
return 0;
}

int max1(int num1, int num2)
{
return num1 > num2 ? num1 : num2;
}

函数参数

函数的实际参数有三种传递方式:

调用类型 调用类型 例子 描述
传值 传值调用 void swap(int x, y) 把实际参数的实际值复制一份给形式参数。修改函数内的形式参数对实际参数没有影响
传址 指针调用 void swap(int * x, int * y) 把实际参数的地址赋值给形式参数。在函数内,该指针用于访问实际参数的地址。这意味着,修改形式参数会影响实际参数
传引用 引用调用 void swap(int &x, &y) 把实际参数的引用赋值给形式参数。在函数内,该引用作为实际参数的别名。这意味着,修改形式参数会影响实际参数

传址与传引用的使用区别,如下:

指针与引用的使用区别

函数调用

函数的调用方式:

  • 嵌套调用
  • 递归调用(直接递归, 间接递归)

参考

http://www.cplusplus.com/doc/tutorial/functions/