C++11 Primer Plus(二)之语法

本文深入探讨了C++中的关系表达式、分支语句、类型别名、字符函数、控制台与文件输入输出等内容,并详细讲解了函数的使用方法,包括函数原型、指针、引用变量、内联函数、函数模板等高级主题。

自己写的C++11 Primer Plus 学习笔记,如有雷同不胜荣幸,如有错误敬请指正


1. 关系表达式与分支语句

1. 逗号运算符

cata = 17,240;复制代码
  • 1

被解释为

(cata = 17),240;复制代码
  • 1

即,将cata设置为17,而240将不起作用,但是

cats = (17,240);复制代码
  • 1

由于括号的优先级最高,该表达式将把cats设置为240(逗号右边的表达式)

2. 类型别名
相同点:

#define BYTE char

typedef char byte复制代码
  • 1
  • 2
  • 3

区别:

typedef char * byte_pointer
byte_pointer pa,pb <<==>> char * pa,*pb //pa,pb a pointer to char

#define FLOAT_POINTER float *
FLOAT_POINTER pa,pb <<==>> float *pa,pb //pa a pointer to float,pb just a float复制代码
  • 1
  • 2
  • 3
  • 4
  • 5

3. cctype中的字符函数

函数名称返回值
isalnum()如果参数是字母数字,即字母或数字,该函数返回true
isalpha()如果参数是字母,该函数返回true
iscntrl()如果参数是控制字符,该函数返回true
isdigit()如果参数是数字(0~9),该函数返回true
isgraph()如果参数是除空格之外的打印字符,该函数返回true
islower()如果参数是小写字母,该函数返回true
isprint()如果参数打印字符(包括空格),该函数返回true
ispunct()如果参数是标点符号,该函数返回true
isspace()如果参数是标准空白字符,如空格,换行符,回车,水平制表符,垂直制表符等,该函数返回true
isupper()如果参数是大写字母,该函数返回true
isxdigit()如果参数是十六进制数字,即0~9,a~f或A~F,该函数返回true
tolower()如果参数是大写字符,则返回其小写,否则返回该参数
toupper()如果参数是小写字符,则返回其大写,否则返回该参数

4. 文本文件与控制台输入

控制台输入:
- 必须包含头文件 iostream
- 头文件iostream定义了一个用于处理输入的istream类
- 头文件iostream声明了一个名为cin的istream变量(对象)
- 必须指明名称空间std
- 可以结合使用cin和运算符 >> 来读取各种类型的数据
- 可以使用 cin 和 get() 方法来读取一个字符,使用 cin 和 getline() 来读取一行字符
- 可以结合使用 cin 和 eof() ,fail() 方法来判断输入是否成功
- 对象 cin 本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值 true,否则被转换为 false

文件输出:
- 必须头文件fstream
- 头文件 fstream 定义了一个用于处理输入的 ifstream 类
- 需要声明一个或多个 ifstream 变量(对象),并以自己喜欢的方式对其进行命名,条件是遵守常用的命名规则
- 必须指明名称空间 std
- 需要将 ifstream 对象与文件关联起来。为此,方法之一是使用 open() 方法
- 使用完文件后,应使用 close() 方法将其关闭
- 可结合使用 ifstream 对象和运算符 >> 来读取各种类型数据
- 可以使用 ifstream 对象和 get() 方法来读取一个字符,使用 ifstream 对象和 getline() 来读取一行字符
- 可以结合使用 ifstream 和 eof(), fail() 等方法来判断输入是否成功
- ifstream 对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值 true


2. 函数–C++的编程模块

通常,函数通过将返回值复制到指定的 CPU 寄存器或内存单元中来将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中储存的数据类型达成一致。函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据

1. 函数原型

原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型以及参数的类型和数量告诉编译器

如下述函数调用

double volume = cube(side);复制代码
  • 1
  • 首先,原型告诉编译器,cube() 有一个 double 参数。如果程序没有提供这样的参数,原型将编译器能够捕获这种错误。
  • 其次,cube() 函数完成计算后,将把放置在指定的位置–可能是CPU的寄存器,也可能是内存中。
  • 然后调用函数将从这个位置获得返回值。
  • 由于原型指出了 cube() 的类型为 double ,因此编译器知道应检索多少个字节以及如何解释它们。

原型应确保以下几点:

  • 编译器正确处理函数返回值
  • 编译器检查使用的参数数目是否正确
  • 编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(可能的话)
cheers(cube(2));复制代码
  • 1
  • 首先,程序将 int 的值 2 传递给 cube(),而后者期望的是 double 类型。编译器注意到,cube() 原型指定了一个 double 类型参数,因此将 2 转换为 2.0 .
  • 接下来,cube() 返回一个 double 值,这个值被用作 cheers() 的参数。编译器再次检查原型,并发现 cheers() 要求一个 int 参数,因此它将返回值转换为整数 8。
  • 通常,原型自动将被传递的参数强制转换为期望的类型。

在 ANSI C 中,括号为空意味着不指出参数—这意味着将在后面定义参数列表。在 C++ 中,不指定参数列表时应使用省略号:

void say_bye(...);//C++ abdication of responsibility复制代码
  • 1

2. 指针

可行(即,常量赋给常量,const 赋给 const):
1. 将常规变量的地址赋给常规指针
2. 将 const 变量的地址赋给指向 const 的指针
注意: 如果数据类型本身并不是指针,则可以将 const 数据或非 const 数据的地址赋给指向 const 的指针,但只能将非 const 数据的地址赋给非const 指针。

尽可能使用const(将指针参数声明为指向常量数据的指针的理由):

  • 这样可以避免由于无意间修改数据而导致的变成错误
  • 使用 const 使得函数能够处理 const 和 非 const 实参,否则将只能接受非 const 数据
const int * ps = &sloth;           // a pointer to const int
int * const finger = &sloth;       // a const pointer to int复制代码
  • 1
  • 2

第一个声明不允许使用 ps 来修改 sloth 的值,但允许将 ps 指向另一个位置
第二个声明使得 finger 只能指向 sloth,但允许使用 finger 来修改 sloth 的值
即,finger 和 *ps 都是 const,而 *finger 和 ps 不是

函数指针::
①. 获取函数地址
获取函数地址很简单:只要使用函数名(后不跟参数)即可。也就是说,如果 think() 是一个函数,则 think 就是该函数的地址。
②声明函数指针:
声明指向函数的指针时,必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的特征标

double pam(int);
double (*pf) (int);
pf = pam;               //pf now points to the pam() function
double x = pam(4);      //call pam() using the function name
double y = (*pf)(5);    //call pam() using the pointer pf复制代码
  • 1
  • 2
  • 3
  • 4
  • 5

二维数组::

ar2[r][c] == *(*(ar2 + r) + c); //same thing

//******************
ar2                 //pointer to first row of an array of 4 int
ar2 + r             //pointer to row r (an array of 4 int)
*(ar2 + r)          //row r (an array of 4 int,hence the name of an array,
                    //thus a pointer to the first int in the row; eg: ar2[r]

*(ar2 + r) + c      //pointer int number c in row r; eg: ar2[r] + c
*(*(ar2 + r) + c)   //value of int number c in row r; eg: ar[r][c]复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3. 函数探幽

1. C++ 内联函数

  • 在函数声明前加上关键字 inline;
  • 在函数定义前加上关键字 inline;

函数调用:

  • 对于普通函数,执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。
  • 对于内联函数,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再条回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。

2. 引用变量

引用变量的主要作用是用作函数的形参
返回引用最重要的一点是,应避免返回函数终止时不再存在的内存单元引用

① 创建引用变量(必须在声明引用时将其初始化)

C++ 使用 & 来声明引用,int & 指的是指向 int 的引用

int rats = 101;
int & rodents = rats;  ==>> int * const pr = &rats;
int * prats = &rats;复制代码
  • 1
  • 2
  • 3

表达式 rodents*prats 都可以同rats 互换,而表达式 &rodentsprats 都可以同 &rats 互换。

② 将引用用作函数参数(按引用传递)

//success
void swapr(int & a,int & b) //use references
{
    int tmp;
    tmp = a;                //use a,b for values of variables
    a = b;
    b = tmp;
}
//success
void swapp(int * p,int * q) //use pointers
{
    int tmp;
    tmp = *p;               //use *p,*q for values of variables
    *p = *q;
    *q = tmp;
}
//fail
void swapv(int a,int b)     //try using values
{
    int tmp;
    tmp = a;                //use a,b for values of variables
    a = b;
    b = tmp;
}

//******************
swapr(wallet1,wallet2);     //pass variables
swapp(&wallet1,&wallet2);   //pass addresses of variables
swapv(wallet1,wallet2);     //pass values of variables复制代码
  • 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

内在区别: 在 swapr() 中,变量 a 和 b 是wallet1 和 wallet2 的别名,所以交换 a 和 b 的值相当于交换 wallet1 和 wallet2 的值;但在 swapv() 中,变量 a 和 b 是复制了 wallet1 和 wallet2 的值的新变量,因此交换 a 和 b 的值并不会影响 wallet1 和 wallet2 的值。

如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。

临时变量,引用参数和const::

如果实参与引用参数不匹配,C++ 将生成临时变量。当前,仅当参数为 const 引用时,C++才允许这样做。

如果引用参数是 const,则编译器将在下面两种情况生成临时变量:

  • 实参的类型正确,但不是左值
  • 实参的类型不正确,但可以转换为正确的类型
long a = 3,b = 5;
swapr(a,b); //swapr(int & a,int & b)表示上面的函数复制代码
  • 1
  • 2

这里是类型不匹配,因此编译器将创建两个临时 int 变量,将它们初始化为 3 和 5,然后交换临时变量的内容,而 a 和 b 保持不变。
简而言之,如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是禁止创建临时变量。

注意: 如果函数调用的参数不是左值或与相应的 const 引用参数的类型不匹配,则 C++ 将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。

尽可能使用 const:

  • 使用 const 可以避免无意中修改数据的编程错误
  • 使用 const 使函数能够处理 const 和非 const 实参,否则将只能接受非 const 数据
  • 使用 const 引用使函数能够正确生成并使用临时变量

右值引用:使用 && 声明

何时使用引用参数::
① 使用引用参数的主要原因:

  • 程序员能够修改调用函数中的数据对象
  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度

② 何时使用引用,指针和按值传递:
Ⅰ 对于使用传递的值而不做修改的函数:

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递
  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向 const 的指针
  • 如果数据对象是较大的结构,则使用 const 指针或 const 引用,以提高程序的效率
  • 如果数据数据对象的类对象,则使用 const 引用。传递类对象的标准方式是按引用传递

Ⅱ 对于修改调用函数中数据的函数:

  • 如果数据对象是内置数据类型,则使用指针。如果看到诸如 fixit(&x) 这样的代码(其中 x 是 int ),则很明显,该函数将修改 x
  • 如果数据对象是数组,则只能使用指针
  • 如果数据对象是结构,则使用引用或指针
  • 如果数据对象是类对象,则使用引用

3. 函数模板

由于模板允许以泛型的方式编写程序,因此有时也会被称为通用编程。由于类型是由参数表示的,因此模板特性有时也被称为参数化类型

template<typename AnyType>
void Swap(AnyType &a,AnyType &b)
{
    AnyType tmp;
    tmp = a;
    a = b;
    b = tmp;
}复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第一行指出,要建立一个模板,并将类型命名为 AnyType 。可以使用 class 代替 typename。
(模板函数不能缩短可执行程序)

① 显示具体化::

显示具体化标准:

  • 对于给定的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及它们的重载版本
  • 显示具体化的原型和定义应以 template<> 打头,并通过名称来指出类型
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板
//no template function prototype
void swap(job &,job &);

//template function prototype
template<typename T>
void swap(T &,T &);

//explicit specialization for the job type
template <> void swap<job>(job &,job &);复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

② 实例化和具体化::
当调用 swap(i,j) (i,j 都为 int 类型)时,编译器会生成一个 swap() 实例。模板并非函数定义,但使用 int 的模板实例是函数定义,这种实例化方式被称为隐式实例化
显示实例化: template void swap<int>(int,int)

试图在同一个文件(或转换单元)中使用同一种类型的显示实例和显示具体化将出错
隐式实例化,显示实例化和显示具体化统称为具体化

③ 重载解析

步骤:

  • 第一步: 创建候选函数列表。其中包含与被调用函数名称相同的函数和模板函数
  • 第二步: 使用候选函数列表创建可行函数列表。这些都是函数数目正确的函数,为此有一个隐式转换序列,其中包含实参类型与相应形参类型完全匹配的情况
  • 第三步: 确定是否有最佳的可行参数。如果有,则使用它,否则该函数调用出错
void may(int);                          //#1
float may(float,float = 3);             //#2
void may(char);                         //#3
char * may(const char *);               //#4
char may(const char &);                 //#5
template<class T> void may(const T &);  //#6
template<class T> void may(T *);        //#7复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中 #4 和 #7 不可行,因为整数类型不能被隐式的转换(即没有显示强制类型转换)为指针类型。

编译器确定函数最佳顺序:

  1. 完全匹配,但常规函数优先于模板
  2. 提升转换(例如:char 和 short 自动转换为 int,float 自动转换为 double)
  3. 标准转换(例如:int 转换为 char,long 转换为 double)
  4. 用户定义的转换,如类声明中定义的转换

补充:

  • 指向非 const 的指针和引用优先于指向 const 的指针和引用
  • 非模板函数将优先于模板函数
  • 显示具体化将优先于使用模板隐式生成的具体化

函数 #1 优于函数 #2,因为 char 到 int 的转换是提升转换,而char 到 float 的转换是标准转换。函数 #3,函数 #5和函数 #6 都优于函数 #1 和 #2,因为它们都是完全匹配的。#3 和 #5 优于 #6,因为 #6 是模板函数。

完全匹配允许的无关紧要转换

从实参到形参
TypeType &
Type &Type
Tyep []*Type
Type(argument-list)Type(*) (argument-list)
Typeconst Type
Typevolatile Type
Type *const Type
Type *volatile Type *

④ 模板函数的发展

template<class T1,class T2>
void ft(T1 x,T2 y)
{
    ...
    decltype(x + y) xpy = x + y;
    ...
}复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Ⅰ 对于 decltype (expression) var

  • 第一步:如果 expression 是一个没有用括号括起的标识符,则 var 的类型与该标识符的类型相同,包括 const 等限定符

    double x = 5.5;
    double y = 7.9;
    double &rx = x;
    const double * pd;
    decltype (x) w; // w is type double
    decltype (rx) u = y; // u is type double &
    decltype (pd) v; // v is type const double *
  • 第二步: 如果 expression 是一个函数调用,则 var 的类型与函数的返回类型相同

    long indeed (int);
    decltype (indeed(3)) m; // m is type int
  • 第三步:如果 expression 是一个左值,则 var 为指向其类型的引用

    double xx = 4.4;
    decltype ((xx)) r2 = xx; //r2 is double &
    decltype (xx) w = xx; //w is double (Stage 1 match)
  • 第四步:如果前面的条件都不满足,则 var 的类型与 expression 的类型相同

    int j = 3;
    int &k = j;
    int &n = j;
    decltype(j + 6) i1;    //i1 type int
    decltype(100L) i2;     //i2 type long
    decltype(k + n) i3;    //i3 type int复制代码
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ?type? ft(T1 x,T2 y)
    auto ft(T1 x,T2 y) -> decltype(x + y)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值