C++ 学习指南基础(二)

目录

 

11. Reference

12. Nullptr and Dynamic Memory Allocation

13. Boolean data type

14. List Initialization

15. Type conversion

16. C++11 Enhancement for Type System

17. Automatic Type Deduction

18. Almost Always Auto (AAA)

19. Automatic Type Deduction: decltype

20. Simplified Memory Model for C++

21.Constant

22. Constant and Pointer

23.Usage of using, typedef, and #define

24. Const function

25. Scope of variable

26.Inline Function

27. Range-based for-loop

28. if and swtich Statement with an Initializer

29. Concepts of Class

30. Create Objects and Access the members


11. Reference

引用:

  1. 引用是在编译的过程中被处理的,实际上就是在编译层面对程序员进行的一个比较友好的语法,而在实现上是由编译器完成了地址的传递,实质上还是指针。

  2. 不能简单的理解为一个别名,我们可以这样用,但是要知道底层就是一个指针变量,是要占用内存空间的,和define是不一样的。

  1. 引用的定义

A reference is an alias for another variable. (引用就是另一个变量的别名)

  1. 声明引用变量的方法

  1. int x;

  2. int& rx = x;

或者

  1. int x, &rx = x;

  2. 引用的性质(非常重要)

Any changes made through the reference variable are actually performed on the original variable (通过引用所做的读写操作实际上是作用于原变量上).

A reference must be initialized in declaration 引用必须在声明的时候初始化。

Once initialized, the name of the reference cannot be assigned to other variables (引用一旦初始化,引用名字就不能再指定给其它变量)

  1. 引用类型做函数的参数:引用传递

You can use a reference variable as a parameter in a function and pass a regular variable to invoke the function. (引用可做函数参数,但调用时只需传普通变量即可)

When you change the value through the reference variable, the original value is actually changed. (在被调函数中改变引用变量的值,则改变的是实参的值)

  1. 例子

5.1. 例1:通过值传参

  1. //pass by value

  2. void swap(int x, int y){

  3. int t;

  4. t=x; x=y; y=t;

  5. }

  6. int main() {

  7. int a=5, b{10};

  8. cout << "Before: a=" << a << " b=" << b << endl;

  9. swap( a, b );

  10. cout << "After: a=" << a << "b=" << b << endl;

  11. return 0;

  12. }

输出结果:

Before: a=5 b=10

After: a=5 b=10

5.2.

例2:通过指针传参

  1. //pass by pointer

  2. void swap(int* x, int* y){

  3. int t;

  4. t=*x; x=y; *y=t;

  5. }

  6. int main() {

  7. auto a{5}, b{10};

  8. cout<< "Before: a=" << a << " b=" << b << endl;

  9. swap( &a, &b );

  10. cout<< "After: a=" << a << "b=" << b << endl;

  11. return 0;

  12. }

输出结果:

Before: a=5 b=10

After: a=10 b=5

5.3.

例3:通过引用传参

  1. //pass by reference

  2. void swap(int& x, int& y){

  3. int t;

  4. t=x; x=y; y=t;

  5. }

  6. int main() {

  7. auto a{5}, b{10};

  8. cout<< "Before: a=" << a << " b=" << b << endl;

  9. swap( a, b );

  10. cout << "After: a=" << a << "b=" << b << endl;

  11. return 0;

  12. }

输出结果:

Before: a=5 b=10

After: a=10 b=5

  1. 引用的性质的补充说明

  1. int a { 0 }, b { 1 };

  2. int& r { a }; // 引用变量 r 在声明的同时就要初始化,r是a的别名

  3. r = 42; // 相当于 a = 42

  4. r = b; // 相当于 a = b; 执行后 a 的值是1

  5. // 此处不是让r变成b的引用

  6. // 引用r声明并初始化后,r就不能再改为其它变量的引用

  7. int& r2 = a; // 继续给a起别名

  8. int& r3 = r; // 声明引用变量 r3,用r初始化

  9. // 这个相当于 int& r3 = a; 因为 r 是 a 的别名

代码示例:引用变量、函数传参

随后展示两个现场编程的代码示例。

你可以打开C++开发环境,跟着视频一起写代码。

  1. 引用变量示例

示例中展示“整型引用”

  1. int x = 0, &rx = x;

示例中展示了“常量指针类型引用”

  1. const char* s = "Hello";

  2. const char*& rs = s;

  1. 函数传参

示例中展示了三个swap函数

  1. void swap( int x, int y );

  2. void swap( int* x, int* y );

  3. void swap( int& x, int& y );

单步调试示例说明

在“函数传参”的三个代码示例中,我们分别看了值传递、指针传递和引用传递三种形参实参调用方式。 void swap( int x, int y ); void swap( int* x, int* y ); void swap( int& x, int& y );

在随后的3个视频展示了程序运行过程中:

  1. 实参、形参存放的位置。以此来观察“形参”“实参”是否是同一个实体

  2. 实参、形参的值的变化。

这3个示例使用了集成开发环境的如下【调试功能】:

  1. 设置程序运行时的【断点】(BreakPoint)。

  2. 【单步运行】(Step Into)。

  3. 【查看】窗口(Watch)

程序必须以“Debug”模式编译; 程序必须以“Debug”模式运行

12. Nullptr and Dynamic Memory Allocation

空指针和动态内存分配

  1. 空指针

1.1. 0带来的二义性问题

  1. C++03中,空指针使用“0”来表示。0既是一个常量整数,也是一个常量空指针。

  2. C语言中,空指针使用(void *)0来表示

  3. 有时候,用“NULL”来表示空指针(一种可能的实现方式是#define NULL 0)

1.2. C++标准化委员会希望“空指针”是一个确定的东西。 C++11中引入保留字“nullptr”作为空指针

  1. Dynamic memory management: Allocate/Release (动态内存管理:分配/释放)

2.1. C++中通过运算符new申请动态内存 new <类型名> (初值) ; //申请一个变量的空间 new <类型名>[常量表达式] ; //申请数组

如果申请成功,返回指定类型内存的地址; 如果申请失败,抛出异常,或者返回空指针(nullptr)。(C++11)

2.2. 动态内存使用完毕后,要用delete运算符来释放。 delete <指针名>; //删除一个变量/对象 delete [] <指针名>; //删除数组空间

13. Boolean data type

布尔数据类型

  1. 布尔类型的定义

布尔(英语:Boolean)是计算机科学中的逻辑数据类型,以发明布尔代数的数学家乔治·布尔为名。它只有两种值,通常是真和假

C++语言在其标准化过程中引入了bool、true和false关键字,增加了原生数据类型来支持布尔数据。

布尔类型的大小(所占的存储空间)依赖于具体的编译器实现。也可以用 sizeof运算符得到其占用的空间 (参见维基百科 https://zh.wikipedia.org/wiki/%E5%B8%83%E6%9E%97_(%E8%B3%87%E6%96%99%E9%A1%9E%E5%9E%8B) )

  1. 布尔类型的用途

布尔数据类型主要与条件语句相关。条件语句用来更改程序控制流。

  1. C++中的布尔类型 C++ keyword: bool, true, false 例如: bool isMyBook; bool isRunning = {false}; //C++11 列表初始化方式 bool isBoy( );

    bool hasLicense(); bool canWork(); bool shouldSort();

  1. 布尔类型与整型的转换(Conversion between bool and int) 转换规则:

    0- false // 整数0和布尔false互相转化

    true - 1 // 布尔true转化为整数1

    non-zero - true // 任意非0整数转化为布尔true

问题:'a' - ?

  1. 关系运算得到布尔值 关系运算(Relational Operation)包括:==, !=, <=, >=, <, > 例如 int a=0, b={1}; //C++11 3 == a; b < a; 3.2 >= b;

if (3 == a) { // blah blah }

  1. 逻辑运算得到布尔值 逻辑运算(Logical Operation)包括:&&, ||, ! int a={0}, b{1}; //C++11 a && b; b || 18; !a;

while (!a) {

 // blah blah

}

  1. 代码示例 #include <iostream> int main() { bool isAlpha; isAlpha = false; if ( !isAlpha ) { std::cout << "isAlpha=" << isAlpha << std::endl; std::cout << std::boolalpha << "isAlpha=" << isAlpha << std::endl; } return 0; }

14. List Initialization

列表初始化

  1. Before C++11 (C++11标准之前的初始化方法)

int x = 0;

int y(2);

char c('a');

int arr[] = { 1,2,3 };

char s[] = "Hello";

C++11 also support the old ways (C++11标准仍然支持旧的初始化方法)

  1. List Initialization (列表初始化)

2.1. List initialization is a new feature for C++11 (列表初始化是C++11的一个新特性)

2.2. List: braced-init-list (“列表”是用花括号括起来的一(些)值)

2.3. 列表初始化的两个分类

  1. Direct list initialization (直接列表初始化)

  2. Copy list initialization (拷贝列表初始化)

//直接列表初始化)

/* Variable initialization */

int x{}; // x is 0;

int y{ 1 }; // y is 1;

/* Array initialization */

int array1[]{ 1,2,3 };

char s1[ 3 ] { 'o', 'k' };

char s3[]{ "Hello" };

//拷贝列表初始化

/* Variable initialization */

int z = { 2 };

/* Array initialization */

int array2[] = { 4,5,6 };

char s2[] = { 'y','e','s' };

char s4[] = { "World" };

char s5[] = "Aloha"; // Omit curly braces (省略花括号)

  1. When do we use list initialization (何时使用列表初始化)

List initialization is also called "unified initialization" (列表初始化也被称为“统一初始化方法”) Variables and arrays are initialized in the same form (变量和数组用同样的形式初始化)

There are still some argues about when to use list-init (目前对于何时使用列表初始化仍然有一些争论) https://stackoverflow.com/questions/18222926/why-is-list-initialization-using-curly-braces-better-than-the-alternatives

  1. A point of view(一种观点)

Prefer {} initialization over alternatives unless you have a strong reason not to(尽量使用列表初始化,除非你有个很好的不用它的理由)

Why: List initialization does not allow narrowing(原因:列表初始化不允许“窄化”,即不允许丢失数据精度的隐式类型转换)

15. Type conversion

类型转换

编程时,经常会遇到数据类型转换的问题,比如将浮点数转换为整数,或者将一个整数转换为字符串后进一步处理。

  1. 类型转换有两种

1.1. 隐式类型转换 由编译器按照数据类型的转换规则自动转换,无需程序员干预。 可能导致数据精度损失,或者转换失败。 应尽量避免使用隐式类型转换

1.2. 显式类型转换(即:强制类型转换) 由程序员用明确的类型转换语法写出类型转换代码。 好处是,程序员知道自己要做什么并且把这个想法明确表达出来。

  1. C风格强制类型转换

语法:(type) value printf("%d", (int) 2.5);

  1. C++风格强制类型转换

语法:static_cast<type> value

out << static_cast<double>(1) / 2;

cout << 1 / 2;

问题:下面代码的输出是什么? cout << static_cast<double>(1 / 2);

  1. 编码规范

    1. Type conversions must always be done explicitly. Never rely on implicit type conversion.

    2. 类型转换必须显式声明。永远不要依赖隐式类型转换

例如:floatValue = static_cast<float>(intValue); // NOT: floatValue = intValue

reinterpret_cast:

允许将任何指针转换为任何其他指针类型。也允许将任何整数类型转换为任何指针类型以及反向转换,是不同类型指针间的转换,不能修改常属性。

const_cast:

用于去除指针变量的常属性,将其转换为一个对应指针类型的普通变量,即从类中移除const、volatile和__unaligned特性。

本小节介绍3个知识点:

布尔数据类型

C++11新引入的变量/数组/对象的初始化方法:列表初始化

类型转换以及C++的关键字 static_cast

一、布尔数据类型

定义布尔数据类型的关键字是 bool

布尔类型的数据只有两个取值:true 和 false

二、列表初始化

列表初始化是使用大括号对变量等实体进行初始化。

例如

int x = { 4 }; int y { 2 }; char s[] = { 'H', 'i' };

三、C++的类型转换

在C++中,应尽量避免编译器的隐式类型转换,而明确地使用显式类型转换。

C++引入了几个类型转换的关键字:static_cast、dynamic_cast、reinterpret_cast、const_cast。

如果要在原生数据类型(也称为基础数据类型)之间进行转换,则使用static_cast关键字。

例如

double x = 3.01; int y = static_cast<int>(x);

16. C++11 Enhancement for Type System

C++11 对类型系统的增强

  1. 类型(Type)是贯穿于计算机程序中的概念

1.1. 数据类型 (Data type) int, long int, double, struct, char , float [], int (f)()…

1.2. 计算机程序构造块(Constructs of a Computer Program)

计算机程序构造块是不同大小粒度的计算机程序组成部分,它包括变量、表达式、函数或者模块等。

  1. 什么是类型系统(What is Type System)

在编程语言中,“类型系统”是将“type”属性指定给不同计算机程序构造块的规则集。

这些类型规范并强制程序员用于数据结构和组件的其它隐式类别(e.g. "string", "array of float", "function returning boolean").

  1. 为什么使用类型系统(Why using Type System)

类型系统可以减少程序中可能出现的bug 类型系统减少BUG的方法是:

  1. 定义不同程序块间的接口

  2. 检查多个块之间是否以一致的方式连接在一起

  1. 静态类型 v.s. 动态类型

程序设计语言的类型系统机制会检查连接在一起的多个块的一致性

上述检查若发生在编译期,称为静态类型 上述检查若发生在运行时,称为动态类型 上述检查若同时存在于编译期和运行时,称为混合类型

17. Automatic Type Deduction

auto (C++11) 自动类型推导:auto关键字

  1. 关键字 auto

C++03及之前的标准种,auto放在变量声明之前,声明变量的存储策略。但是这个关键字常省略不写。 C++11中,auto关键字放在变量之前,作用是在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型

例如:

int a = 10;

auto au_a = a;//自动类型推断,au_a为int类型

cout << typeid(au_a).name() << endl;

  1. auto的使用限制

2.1. auto 变量必须在定义时初始化,这类似于const关键字

auto a1 = 10; //正确

auto b1; //错误,编译器无法推导b1的类型

b1 = 10;

2.2. 定义在一个auto序列的变量必须始终推导成同一类型

auto a4 = 10, a5{20}; //正确

auto b4{10}, b5 = 20.0; //错误,没有推导为同一类型

2.3. 如果初始化表达式是引用或const,则去除引用或const语义。

int a{10}; int &b = a;

auto c = b; //c的类型为int而非int&(去除引用)

const int a1{10};

auto b1 = a1; //b1的类型为int而非const int(去除const)

2.4. 如果auto关键字带上&号,则不去除引用或const语意

int a = 10; int& b = a;

auto& d = b;//此时d的类型才为int&

const int a2 = 10;

auto& b2 = a2;//因为auto带上&,故不去除const,b2类型为const in

2.5. 初始化表达式为数组时,auto关键字推导类型为指针。

int a3[3] = { 1, 2, 3 };

auto b3 = a3;

cout << typeid(b3).name() << endl; //输出int * (输出与编译器有关)

2.6. 若表达式为数组且auto带上&,则推导类型为数组类型。

int a7[3] = { 1, 2, 3 };

auto& b7 = a7;

cout << typeid(b7).name() << endl; //输出int [3] (输出与编译器有关)

2.7. C++14中,auto可以作为函数的返回值类型和参数类型

18. Almost Always Auto (AAA)

尽量使用auto

  1. Why Almost Always Auto (为何尽量使用auto)?

Using auto are for correctness, performance, maintainability, robustness—and convenience (使用auto是为了代码的正确性、性能、可维护性、健壮性,以及方便),例如:保证在声明变量时即初始化

  1. 特殊情况的处理

我们在使用auto时有时会遇到一些特殊情况。

2.1. "int x = 3;" 能变成auto形式吗? 当我们非常希望能够在变量定义的时候,【明确】地指出变量的类型,而且不希望随便更改其类型,那么我们可以使用下面的方法:

auto x = int {3}; // 初始化列表

auto y = int {3.0}; // 编译器报错,初始化列表不能窄化

auto z = int (3.0); // C风格的强制类型转换,z的值是整数3

2.2. auto和初始化列表一起用 要避免在一行中使用直接列表初始化和拷贝列表初始化,也就是,下面的代码是有问题的:

auto x { 1 }, y = { 2 }; // 不要同时使用直接和拷贝列表初始化

  1. 例子

Classic C++ Style (经典C++风格) Modern C++ Style(现代C++风格)
int x = 42; auto x = 42;
float x = 42.; auto x =42.f;
unsigned long x = 42; auto x = 42ul;
std::string x = "42"; auto x = "42"s; //c++14
chrono::nanoseconds x{ 42 }; auto x = 42ns; //c++14
int f(double); auto f (double) -> int; auto f (double) { // }; auto f = { /*… */ }; //匿名函数

注:关于AAA的内容,大部分来自“Guru of the Week (GotW)” Guru of the Week (GotW)

https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/

能用auto声明C风格的数组吗?

  1. // 包含头文件

  2. // 声明主函数

  3. // ......

  4. auto x[] = {1,2,3};

不能,因为auto类型不能出现在顶级数组类型中

19. Automatic Type Deduction: decltype

自动类型推导:decltype关键字

  1. 关键字decltype的用法 decltype利用已知类型声明新变量。 有了auto,为什么还要整出一个decltype?原因是,我们有时候想要从表达式的类型推断出要定义的变量类型,但不想用该表达式的值初始化变量。 decltype是在编译期推导一个表达式的类型,它只做静态分析,因此它不会导致已知类型表达式执行。 decltype 主要用于泛型编程(模板)

  1. 代码示例

#include<iostream> using namespace std; int fun1() { return 10; } auto fun2() { return 'g'; } // C++14 int main(){ // Data type of x is same as return type of fun1() // and type of y is same as return type of fun2() decltype(fun1()) x; // 不会执行fun1()函数 decltype(fun2()) y = fun2(); cout << typeid(x).name() << endl; cout << typeid(y).name() << endl; return 0; }

  1. decltype与auto的对比

decltype和auto都是C++11自动类型推导的关键字。它们有很多差别: auto忽略最上层的const,decltype则保留最上层的const auto忽略原有类型的引用,decltype则保留原有类型的引用 对解引用操作,auto推断出原有类型,decltype推断出引用;

4.auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。

C++11的自动类型推导:类型系统、auto与decltype

C++11的类型推导关键字auto

C++11的类型推导关键字decltype

但是,auto与初始化列表结合,又有一些坑。你在写代码时如果经常将auto与列表初始化一起使用,那么会遇到一些问题。本节只介绍auto的常见用法。auto与初始化列表结合的坑,得由你自己去踩了。

为了说明auto有多么复杂,这里摘取 https://cppreference.com 网站列出的auto的语法格式:

auto variable initializer (1) (C++11 起)
auto function -> return type (2) (C++11 起)
auto function (3) (C++14 起)
decltype(auto) variable initializer (4) (C++14 起)
decltype(auto ) function (5) (C++14 起)
auto :: (6) (概念 TS)
cv(**可选)** auto ref**(可选) parameter (7) (C++14 起)
template < auto Parameter > (8) (C++17 起)
cv(可选 ) auto ref(可选) [ identifier-list ] initializer ; (9) (C++17 起)

20. Simplified Memory Model for C++

C++的简化内存模型Simplified Memory Model (简化的C++内存模型)

  1. Stack (栈) 编译器自动分配释放

  2. Heap (堆) 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收

  3. Global/Static (全局区/静态区) 全局变量和静态变量的存储是放在一块的。 可以简单认为: • 程序启动全局/静态变量就在此处 • 程序结束释放

  4. Constant (常量区) 可以简单理解为所有常量都放在一起 该区域内容不可修改Example of memory allocation (C++程序的内存示例) 堆向高地址方向生长 栈向低地址方向生长

Location of a variable (变量存放位置)

数组内存模型

对于数组a[], a是数组a[]的首地址的别名

要访问每个数组元素的值,使用a[0], a[1],…

要访问一个地址所存的内容,使用 “*”

访问数组中第一个元素可以使用 *(a+0)

访问数组中第二个元素 *(a+1)

21.Constant

  1. 常量 常量是程序中一块数据,这个数据一旦声明后就不能被修改了。

如果这块数据有一个名字,这个名字叫做命名常量;比如 const int A = 42; 其中A就是命名常量; 如果这块数据(这个常量)从字面上看就能知道它的值,那它叫做“字面常量”,比如上面例子中的“42”就是字面常量

  1. 编码规范

  1. Named constants (including enumeration values) must be all uppercase using underscore to separate word. 符号常量(包括枚举值)必须全部大写并用下划线分隔单词

例如:MAX_ITERATIONS, COLOR_RED, PI

C++中我们提到的变量,一定是可以重赋值的

  1. int x = { 42 }; // x是变量

  2. x = 24 // 重新赋值

就我所知,C++中允许一次性赋值东西有常量,或者重载了=运算符(赋值运算符)的类的对象。

  1. const int y = { 42 }; // 这个在C++中叫做常量,在编译期就确定了值

  2. const int z = x; // z也是常量,在运行期才能确定值。

  3. y = 42 + 1; // 不允许

  4. z ++; // 不允许

22. Constant and Pointer

常量和指针

常量和指针搅合在一起,成为一个比较麻烦的问题。就像你想分清楚“己巳已”不容易一样,想搞清楚“指针常量”、“常量指针”、“常量指针常量”这种拗口的东西,总得花点心思。

问题还不是记性好不好,因为指针和常量搅合在一起,还涉及到函数传参、C风格数组、字符串常量这些应用场景,就更得仔细一些。所以,你应该仔细看看代码示例的演示视频。

  1. 指针 指针是一个地址,它长得像 0x8FFF 这个样子。地址呢,就是某个内存位置的一个编号。那这个位置的内存是可以存放一些数据的。这些数据就叫做“指针所指的数据”或者“指针指向的数据”

  1. 常量和指针 我们把指针放到一个变量里面,就是指针变量;

我们把指针放到常量中,就是指针常量;

那如果一个指针(也就是地址,比如0x8FFF)所指的数据(也就是0x8FFF这个内存位置存放的数据)是常量,这个指针被称为常量指针。

所以,有一种东西,叫做“常量指针常量”。就是说,一个常量中存着一个指针,这个指针又指向另外一个常量。

  1. Pointer to Constant (常量指针/常指针) 特征:指针所指向的内容不可以通过指针的间接引用(*p)来改变。

const int x = 1;

const int* p1;

p1 = &x; //指针 p1的类型是 (const int*)

*p1 = 10; // Error!

char* s1 = "Hello"; // Error!

const char* s2 = "Hello"; // Correct!

  1. Pointer Constant (指针常量) “指针常量”的含义是:指针本身的内容是个常量,不可以改变

int x = 1, y = 1;

int* const p2 = &x; //常量 p2的类型是 (int*)

*p2 = 10; // Okay! à x=10

p2 = &y; // Error! p2 is a constant

  1. 指针和常量的总结

const int * x

int * const y

在前先读,在前不变

  • (指针)和 const(常量) 谁在前先读谁 ;* 代表被指的数据,名字代表指针地址

const在谁前面谁就不允许改变。

  1. int** x = 0, * const px = &x;

  2. const int* const &pp = px;

  1. 按*和const的出现顺序,其和“指针常量”有关; *

  2. 又因为出现&,所以其为“指针常量的引用”,即px的别名; 同时,该pp具有const属性,即:不可以通过pp的间接引用(*pp)改变x的值。

  3. 如果改一下代码: int x = 0, const px = &x; /const/ int const& pp = px; *pp = 1; 则,x的值变为1.

23.Usage of using, typedef, and #define

using, typedef, and #define的用法

  1. using 声明新的类型名称

当我们声明这样一些变量时:

const unsigned long int * p;

const unsigned long int * q;

const unsigned long int * r;

我们会觉得很麻烦。

那有没有一种办法使得 p, q, r的类型声明简便一点呢?

我们在C语言里面学了 typedef,它可以声明一个新的类型名。

typedef const unsigned long int * MyPointer;

MyPointer p;

C++11中为 using 关键字赋予了一个类型声明的新功能

using ConstPointer = const unsigned long int *;

ConstPointer p;

ConstPointer q;

ConstPointer r;

using的写法比typedef的写法更加直观,所以,我们应尽量使用using声明新类型名。而且当涉及到模版类型名时,只能使用using。

  1. 编码规范

  1. Names representing types must be in mixed case starting with upper case.

  2. 代表类型的名字必须首字母大写并且其它字母大小写混合

例如:Line, SavingsAccount

24. Const function

常函数

形式: void fun() const {} 构造函数和析构函数不可以是常函数 特点:①可以使用数据成员,不能进行修改,对函数的功能有更明确的限定; ②常对象只能调用常函数,不能调用普通函数;

有 const 修饰的成员函数(指 const 放在函数参数表的后面,而不是在函数前面或者参数表内),只能读取数据成员,不能改变数据成员;

没有 const 修饰的成员函数,对数据成员则是可读可写的。

常函数在类内部以及外部定义的区别:若定义在外部,则必须在类内部声明为常函数,在类外定义函数说明行后面不需要加 const

Java常量与C++常量的区别

Java语言和C++语言中都可以声明常量。

Java使用关键字 final,C++使用关键字 const。在C++11之后,还有一个关键字 constexpr 用于声明编译器常量

java的常量与C++的常量有什么区别呢?

java:

  1. final int a; // 声明常量 a。final 值声明的时候可以不做初始化

  2. a = 10; // 第一次赋值时初始化

  3. a = 12; // 错误。不允许二次赋值

c++:

  1. const int a = 10; // 声明常量a

  2. const int b; // 错误。命名常量必须在声明的同时初始化

  3. a = 42; // 错误。常量一旦声明,就不能再修改

25. Scope of variable

  1. 变量的作用域分类

a. 全局作用域:全局变量 b. 局部作用域:局部变量

局部作用域可以分为:文件作用域、函数作用域以及函数内部的块作用域。

如果外部代码块与其内嵌代码块有同名的变量,那么会产生同名覆盖这种现象。此时要遵循“就近原则”来判断哪个同名变量起作用

  1. Unary Scope Resolution (一元作用域解析运算符)

If a local variable name is the same as a global variable name, you can access the global variable using ::globalVariable. (局部变量名与全局变量名相同时,可使用 :: 访问全局变量)

The :: operator is known as the unary scope resolution.(:: 这个运算符被称为一元作用域解析运算符)

例子

 #include <iostream>
 ​
 int v1 = 10;
 ​
 int main() {
     
 int v1 = 5;
 ​
 std::cout << "local variable v1 is "  << v1   << std::endl;
 ​
 std::cout << "global variable v1 is " << ::v1 << std::endl;
 ​
 return 0;
     
 }

重载函数

重载函数是在同一个名字空间中存在两个或者多个具有相同名字的函数所构成的语法现象。

调用重载函数的语句,是由编译器在编译期确定的。

编译器判断某个函数调用语句所对应的重载函数时,判断依据是函数参数的类型、个数和次序。

如果编译器无法判定,就会报告二义性错误。

带有默认参数值的函数

函数的参数可以指定默认值。

指定默认值时,要保证带有默认值的参数要位于函数参数列表的右侧。

调用带有默认参数值的函数时,如果不指定带有默认值的参数,则该参数自动被赋为默认值

C++规定(C++03/C++11): A default argument shall not be redefined by a later declaration (not even to the same value). (函数重定义/声明时,不允许重定义默认参数)

 int Add (int a, int b = 3);  // 原型声明
 ​
 int Add (int a, int b = 3) { // 错误!不能重定义默认参数值,       
           // 尽管与原型声明相同
 }

26.Inline Function

内联函数

  1. 普通函数的优缺点

1.1. Pros(优点): 易读易维护

1.2. Cons (缺点): 调用时有开销

函数调用时:参数及部分CPU寄存器的内容进栈,控制流跳转

函数返回时:返回值及寄存器值出栈,控制流跳转

  1. 使用内联函数

目的:减小函数调用开销

方法:代码插入到调用处

结果:导致程序变大

  1. 定义内联函数

定义函数时,在函数类型前面加上 inline 关键字,则该函数就成为内联函数

一般而言,内联函数的声明和定义都在一起。我们很少将内联函数的声明和定义分开编写

// 定义内联函数

inline int max (int a, int b) {

 return (a > b ? a : b);

}

// Calling (调用内联函数)

int x = max (3, 5);

int y = max (0, 8);

// Inline expansion(内联展开)

int x = (3 > 5 ? 3 : 5);

int y = (0 > 8 ? 0 : 8);

  1. 内联函数的使用

编译器在遇到内联函数的调用时,会将内联函数的函数体展开到调用位置,从而避免函数调用的开销。

一般来说,内联函数只有在需要考虑程序运行性能的环境中才使用。

程序员所用的 inline 关键字,只是对编译器的一个请求。内联函数是否展开,是由编译器决定的。

  1. 将内联函数的声明和定义分离

在C++标准7.1.2.4节有如下说明

An inline function shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case (3.2). [ Note: A call to the inline function may be encountered before its definition appears in the translation unit. —end note ]

内联函数应在每个翻译单元中定义。在该翻译单元中它遵循“单一定义规则(ODR)”,并且所有该内联函数定义必须完全相同。[注释:在翻译单元中可能会在内联函数定义出现之前就有调用该内联函数的语句]

因此,内联函数声明和定义分类的用法如下:

#include <iostream>

inline void foo();

int main() {

foo();

}

inline void foo() {

std::cout << "Hi\n";

}

https://stackoverflow.com/questions/11428147

总结:特殊函数:重载、默认参数值与内联

本小节主要介绍函数的一些特殊形态: a. 重载函数 b. 带有默认参数值的函数 c. 内联函数

变量的作用域决定着变量生存或者可见的范围。我们在给变量或者函数起名字的时候,除了循环变量等特殊情况,尽量不要用一两个字母的名字

重载函数就是名字完全一样,但是参数类型、个数或者顺序不一样的函数。重载函数主要用来对付不同类型的数据。

默认参数值则是给函数参数一个默认值,如果调用这个函数的时候不写这个参数,那么就用默认值。默认参数值必须按参数从右往左声明。

依靠参数个数来区分的重载函数,如果与函数默认参数值混合在一起会产生二义调用的问题,编译器搞不清楚你想调用哪个函数。所以,初学者避免将重载函数与函数默认参数一起用。

内联函数主要目的是减小函数调用时的开销。

27. Range-based for-loop

基于范围的for循环

  1. 基于范围的for循环的语法

1.1. 语法

for( 元素名变量 : 广义集合) { 循环体 }

a.“元素名变量”可以是引用类型,以便直接修改集合元素的值; b. “元素名变量”也可以是const类型,避免循环体修改元素的值 c. 其中“广义集合”就是“Range(范围)”,是一些元素组成的一个整体

1.2. “广义集合”例子

auto a1[] { 1, 3, 5, 7 };

std::array<int, 4> a2 { 2, 4, 6, 8};

std::vector< int > v = { 42, 7, 5 };

std::vector< std::string > s { "Hello", "World", "!"};

  1. 用法

想要操作某个广义集合中的所有元素,那么只需要关心 a. 从集合中取出某个元素 b. 保证所有元素都被遍历

例:把数组a的元素都输出到屏幕上;然后把数组的元素翻倍

int a[] = { 2,1,4,3,5 };

for (auto i : a) {

 std::cout << i << std::endl;

}

for (auto& i : a) {

 i = 2 * i;

}

  1. 限制

基于范围的循环仅限于for语句 do…while(); 和while(){} 不支持基于范围的循环

28. if and swtich Statement with an Initializer

带有初始化器的if和switch语句

  1. if statement with an initializer (带有初始化器的if语句)

1.1. if without an initializer (不带初始化器的if语句)

int foo(int arg) { // do something

return (arg);

}

int main() {

auto x = foo(42);

if (x > 40) {

 // do something with x

} else {

 // do something with x

}

// auto x = 3;

}

1.2. if with an initializer (带有初始化器的if语句)

int foo(int arg) { // do something

return (arg);

}

int main() {

// auto x = foo(42);

if (auto x = foo(42); x > 40) {

 // do something with x

} else {

 // do something with x

}

auto x = 3; // 名字 x 可重用

}

  1. Why we need if with a initializer? (为何要使用带有初始化器的if语句)

The variable, which ought to be limited in if block, leaks into the surrounding scope (本应限制于if块的变量,侵入了周边的作用域)

The compiler can better optimize the code if it knows explicitly the scope of the variable is only in one if block (若编译器确知变量作用域限于if块,则可更好地优化代码)

  1. "switch" statements with an initializer (带有初始化器的switch语句)

Synatx: switch(initializer;variable)

switch (int i = rand() % 100; i) {

case 1:

 // do something

default:

 std::cout << "i = " << i << std::endl;
 ​
 break;

}

29. Concepts of Class

类的概念

  1. OO Programming Concepts (面向对象编程的概念)

Object-Oriented Programming (OOP) involves programming using objects. (OOP利用对象进行程序设计)

An object represents an entity in the real world that can be distinctly identified. (一个对象表示现实世界中一个独一无二的实体)

For example, a student, a desk, a circle, a button, and even a loan can all be viewed as objects.

例如,一个学生,一张桌子,一个圆圈,一个按钮,甚至一笔贷款都可以看成是对象

  1. Features of OO (面向对象的特征)

(1) Abstraction (抽象)

(2) Polymorphism (多态)

(3) Inheritance (继承)

(4) Encapsulation (封装)

我们将这四个单词的首字母放在一起: APIE

中文含义是:一块甜饼。 这样记忆就方便多了。

  1. What does an object consist of? (对象由什么构成)

An object has a unique identity, state, and behaviors.(对象具有唯一的标识、状态和行为)

The state of an object consists of a set of data fields (also known as properties) with their current values. (对象状态由数据域(也称为“属性”)及其当前值构成)

The behavior of an object is defined by a set of functions. (对象的行为由一组函数定义)

  1. How to define Objects (如何定义对象)

An object is an instance of a class (对象是类的实例)

A class includes (类包含):

(1) data fields,defined by variables(由变量定义的数据域)

(2) Behaviors, defined by functions(由函数定义的行为)

A class has two special types of functions (类中有两种特殊的函数)

(1) constructors : which are invoked automatically when constructing objects from the class. (构造函数:在创建对象时被自动调用)

(2) destructors : : which are invoked automatically when the object is destroyed. (析构函数:在对象被销毁时被自动调用)

析构/构造函数简写:ctor 和 dtor

30. Create Objects and Access the members

创建对象并访问对象成员

  1. Constructors(构造函数)

class Circle { public: // The radius of this circle double radius;

// Construct a circle object Circle() { radius = 1; }

// Construct a circle object Circle(double newRadius) { radius = newRadius; }

// Return the area of this circle double getArea() { return radius * radius * 3.14159; } };

Ctors的特点:

(1) Automatic invocation(自动调用)

(2) Has the same name as the defining class (与类同名)

(3) NO return value (including "void"); (无返回值)

(4) Can be overloaded (可重载)

(5) May have no arguments (可不带参数)

A class may be declared without ctors (类可不声明构造函数)

(1) A no-arg constructor with an empty body is implicitly declared in the class. (编译器会提供一个带有空函数体的无参构造函数)

(2) This constructor, called a default constructor is provided automatically only if no constructors are explicitly declared in the class. (只有当未明确声明构造函数时,编译器才会提供这个构造函数,并称之为“默认构造函数”)

  1. Constructing Objects (创建对象)

Without Arguments: (无参数)

创建一个对象,不传递参数。

Circle circle1; // 正确,但不推荐这样写 Circle circle2(); // 错误!C++编译器认为这是一个函数声明 Circle circle3{}; // 正确,推荐写法。这里面明确显示用空初始化列表初始化circle3对象(调用Circle默认构造函数)

With Arguments: (带参数)

创建一个对象,创建的同时传递参数,用于初始化该对象

Circle circle2{ 5.5 }; // C++11 列表初始化 // 带有窄化检查(narrowing check)

Circle类的声明:

class Circle { public: double radius; Circle() { radius = 1; } Circle(double newRadius) { radius = newRadius; } //…… };

  1. Object Member Access Operator(对象访问运算符)

To access the data & functions of an object: (访问对象中的数据和函数)

the dot operator (.), namely, the object member access operator. ( 点运算符,也称为对象成员访问运算符)

  1. A Simple Circle Class

#include <iostream> using namespace std; class Circle { public: // The radius of this circle double radius; // Construct a circle object Circle() { radius = 1; } // Construct a circle object Circle(double newRadius) { radius = newRadius; } // Return the area of this circle double getArea() { return radius * radius * 3.14159; } };

int main() { Circle circle1; Circle circle2(5.0);

cout << "The area of the circle of radius " << circle1.radius << " is " << circle1.getArea() << endl; cout << "The area of the circle of radius " << circle2.radius << " is " << circle2.getArea() << endl;

// Modify circle radius circle2.radius = 100.0; cout << "The area of the circle of radius " << circle2.radius << " is " << circle2.getArea() << endl;

return 0; }

发布了6 篇原创文章 · 获赞 0 · 访问量 211

猜你喜欢

转载自blog.csdn.net/LYR1994/article/details/105337566
今日推荐