C++ Primer Plus基础知识部分快速通关

Posted we-unite

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Primer Plus基础知识部分快速通关相关的知识,希望对你有一定的参考价值。

第二章

  • 在 C++中,\\(main\\)函数括号中使用void关键字表明拒绝任何参数,而空括号表示对是否接受参数保持沉默。
  • 连续赋值将从右向左进行。
  • 输出拼接长字符串可以如下书写:
    //当代码很长而追求风格的时候,这样操作。
cout << "1234567"
     << "7654321"
     << endl;
  • 类描述了一种数据类型的全部属性(包括可用其来执行的操作),对象是根据这些描述创建的实体。

第三章

  • 数据输入/常数可以以十进制、十六进制、二进制书写。
#include <iostream>
using namespace std;

int main()

    int a = 10, b = 0xf, c = 0b10100;
    cout << a << b << c << endl;
    return 0;

  • cout提供控制符以八进制、十进制与十六进制显示整数。
#include <iostream>
using namespace std;
int main()

    using namespace std;
    int chest = 42;
    int waist = 42;
    int inseam = 42;
    cout << "Monsieur cuts a striking figure !" <<endl;
    //所有设置,在更改之前将会一直延续
    //默认显示十进制
    cout << "chest = " << chest << "(decimal for 42 ) " << endl;
    cout << hex; // 显示十六进制
    cout << "\'waist = " << waist <<"(hexadecimal for 42)" << endl;
    cout << oct; // 十进制
    cout << "inseam = " << inseam <<: " (octal for 42 ) " << endl;
    return 0 ;

  • 通用字符名

ISO 10646,Unicode 与 UTF-8 的那些事

  • 常量定义,建议使用const限定而非#define,且必须在定义时赋值,否则即乱码数字,后续无法修改。
  • 浮点数赋值直接赋,不认识后缀 f
  • auto声明,自动根据初始值类型推断变量类型。

第四章

数组

  • 数组创建,数组大小必须为常量,不能是变量。
    • 数组是一种复合类型,是依托于其他类型来创建的(C 称之为派生,而 C++对类关系使用派生这一术语)
  • 数组初始化,如果在定义时仅对一部分元素初始化,则其他元素将被设置为 0

字符串

  • 拼接字符串常量。字符串过长时允许拼接字符串字面值。
    • 任两个由空白分隔符(空格、\\t\\n)分割的字符串常量都会被自动拼接为一个。
  • getgetline,二者使用方式基本相同,只是前者继承 cin 的特性,不会读取换行符后再从字符串中删除,而是把换行符留在输入队列中。
/*在没有任何参数的情况下
 *cin.get()会读取下一个字符,即使是换行符
 *因此可用以清空*/
cin.get(name , ArSize);// read first line
cin.get();// read newline
cin.get(dessert. Arsize ) ;// read second line
//前两行可以合并为:
//cin.get(name, ArSize).het()
  • 其他类型字符的使用
    • wchar_t
    • char16_t
    • char32_t
  • C++string提供字符串翻转函数。

结构体

  • 结构体初始化
    • 按声明顺序将列表声明为结构体初始化
    • 指定对应初始化
    • 构造函数初始化
#include <iostream>
using namespace std;

struct student

    int id;
    string name;
    short scores[3];
    void student(int id,string name,short scores[])
    
        this->id=id;
        this->name=name;
        for(char i=0;i<3;i++)
        
            this->scores[i]=scores[i];
        
    
;

int main()

    student a=2021110884,"QJGZ",150,150,150;
    strdent b=.id=2021110884,.name="陈一豪",.scores=150,150,150;
    student c(2021110884,"QJGZ",150,150,150);

    return 0;

注意,前两种方法不能使用在有构造函数的结构体中,否则会报错“指示符不能用于非聚合类型”。

对于所有非聚合类型,不能使用初始化列表(即用列表形式初始化,前两种方法是也)。

聚合类型定义如下:

  • 数组
  • 不包含(构造函数、private 和 protect、基类、虚函数)的类、结构体和联合体

共用体

举例:一个小商品目录,一些商品 id 为字符串,另一些为数字:

union id

    long id_num;
    char id_str[32];
;
struct good

    char brand[20];
    int type;
    id id_val;
;

同时,有匿名共用体:


struct good

    char brand[20];
    int type;
    union
    
        long id_num;
        char id_str[32];
    
;

匿名共用体没有自己的名称,成员将成为位于同一位置的不同变量,被视为上一级的成员。

枚举

啥 JB 东西,看不懂!!!

指针

  • int*被理解为一种复合类型而存在,但int* a, b被解释为一个指针和一个整型

哪个 TM 大聪明的主意?

指针、数组与指针算术

指针与数组基本等价的原因在于指针算数和 C++内部处理方式。

算术:

  • 整型变量递增+1,指针变量递增加的是指向的类型的字节数。这同时表明,C++将数组名解释为地址。
  • 可以以相同的形式使用指针与数组名。
    • 方括号数组表示
    • 加偏移量而后解引用
  • 注意:
    • 数组名直接取地址为整个数组作为内存块的地址,递增时加的是整块大小
    • 数组名本身是首个元素的地址,递增时加的是元素大小
    • 二者初值相同

变量存储方式

  • 局部存储
  • 静态存储
    • 定义为全局变量
    • 使用static关键字,如static int c=3;
  • 动态存储

数组替代

  • vector
    • 分配在自由存储区或堆中。
    • 功能强大,效率略低
  • array
    • 长度固定如数组,和数组一样分配在栈上而非自由存储区,因而效率同于数组,但更方便安全。
#include <array>
using namespace std;

int main()

    array<int,5> a;
    int i;
    for(i=0;i<5;i++)
    
        cin>>a[i];
    
    for(i=0;i<5;i++)
    
        cout<<a[i];
    
    return 0;

第五章

递增递减运算符

  • 写在后边,则变量先使用,再加/减
  • 写在前边,则变量先加/减,再使用
  • 避免使用不完整的表达式
    //完整的表达式
    i=0
    while(i++<10)
    
        cout<<i<<endl;
    
    /*在上例中,先执行判断,而后递增,再输出,因而输出结果为[1,10]
     *这是因为i++<10本身是一个完整表达式
     */

    //不完整表达式
    y=(4+x++)+(6+x++);
    /*上式中,(4+x++)并不是一个完整表达式,无法保证执行完本处计算后x即刻+1
     *因而整条语句结果并不确定
     */

解释:

  • 副作用指计算表达式时对某些东西进行了修改
  • 顺序点指程序过程中一个点,在这里将在进入下一步之前对所有副作用进行评估。

在 C++中,语句的分号是一个顺序点,在下一步之前会把所有副作用造成的修改弄到位。
上例中,前者即有顺序点,执行之前就完成了副作用;后者顺序点在分号,因而副作用结果并不确定。

本身即为运算表达式,有返回值,返回值即上所谓“使用”。

指针与递增递减

递增递减作用不赘。

前缀递增递减与解引用运算优先级相同,因而从内向外解释:

    int a[2]=1,2,*pt=a;
    cout<<*++pt<<*(++pt)<<endl;
    //输出结果,二者相等,即*++pt先执行递增,而后经过顺序点,再解引用,实际为a[1]=2

后缀递增递减优先级高于解引用,因而*pt++结果与上亦同。

逗号运算符

逗号运算符允许把两个表达式放一行,表达式的值是后半句的值。

普通赋值运算式的值为右值。

#include<iostream>
using namespace std;
int main()

    int a;
    a=1,2;
    cout<<a<<endl;//输出1,2不起作用
    a=(1,2);
    cout<<a<<endl;//输出2
    return 0;

循环

for循环与while循环的本质是相同的:

for(init-expression; test-expression; update-expression)

    statements


//等价于下述表达
init-expression;
while (test-expression)

    statements;
    update-expression;

区别:

  • for循环中省略测试表达式时默认为 true,而while中禁止此种行为
  • 在循环体中有continue语句时,二者表现不再完全等价,稍有不同

循环与文本输入

  • 逐个读取字符需要检查遇到的每个字符包括空格、制表、换行等非显示字符。此时应当使用cin.get()函数。
  • 函数cin.get(ch)读取输入中的下一个字符(包括空格)并赋值给 ch,可以替换cin>>ch

文件尾(EOF)条件

重要性

  • 很多操作系统(包括 Unix、Linux 和 Windows 命令提示符模式)都支持重定向,允许用文件替换键盘输入。

例如,假设在 Windows 中有一个名为 gofish.exe 的可执行程序和一个名为 fishtale 的文本文件,则可以在命令提示符模式下输入下面的命令:

gofish < fishtale

来实现以文本文件代替输入。

  • 很多操作系统都允许通过键盘来模拟文件尾条件。
    • 在 Unix 中,可以在行首按下 Ctrl+D 来实现;
    • 在 Windows 命令提示符模式下,可以在任意位置放 Ctrl+Z 和 Enter。
    • 有些 C++实现支持类似的行为,即使底层操作系统并不支持。

键盘输入的 EOF 概念实际上是命令行环境遗留边倒下来的。

然而:

  • 用于 Mac 的 Symantec C++模拟了 Unix,将 Ctrl+D 视为仿真的 EOF
  • Metrowerks Codewarrior 能够在 Macintosh 和 Windows 环境下识别 Ctr1+Z
  • 用于 PC 的 Microsoft Visual C++、Borland C++ 5.5 和 GNU C++都能够识别行首的 Ctrl+Z,但用户必须随后按下回车键。

总之,很多 PC 编程环绕都将 CtrI+Z 视为模拟的 EOF,但具体细节(必须在行首还是可以在任何位置,是否必须按下回车键等)各不相同。

  • 如果编程环境能识别 EOF,则既可以使用重定向文件输入也可以用键盘,岂不美哉?

实现

检测到 EOF 后,cin 将两位(eofbitfailbit)均设置为 1。(可通过成员函数bool cin.eof()bool cin.fail()查看情况,且是在读取之后查看。)

第六章

逻辑运算符相关

  • 逻辑与/或的优先级均低于关系运算符
    • 逻辑与的优先级高于逻辑或
  • 逻辑非的优先级高于所有关系运算符和算术运算符,因而要对表达式求反必须括起来。
  • 另一套表示方式
运算符 另一种表示
&& and
|| or
! not

字符函数库

cctype(C 语言中的 ctype.h)可以用来确定字符是否为大/小写字母、数字、标点等。其优点在于更简单、更通用(字母、数字之类在不同的编码方式之下不一定都像 ASCII 中一样的连续分布)。包含的函数如下:

namespace std 
    int isalnum(int c);
    int isalpha(int c);
    int isblank(int c);
    int iscntrl(int c);
    int isdigit(int c);
    int isgraph(int c);
    int islower(int c);
    int isprint(int c);
    int ispunct(int c);
    int isspace(int c);
    int isupper(int c);
    int isxdigit(int c);
    int tolower(int c);
    int toupper(int c);

第七章

基础知识

  • 函数原型:说明函数名、形参类型/名称、返回值类型
    • 其实就是\\(main()\\)之前的函数声明
    • 函数原型中的函数特征标(参数列表)可以省略标识符(形参名称)而只是指出其类型

其实就是那个二锅头,兑的那个白开水

  • 函数定义:函数的本体实现

函数与数组

//函数定义
int sum_arr(int arr[], int n);//对数组中元素求和

//函数调用
int a[10]=……;//一堆破数
int sum=sum_arr(a,10);

以上定义的函数,第一个形参是指针而非数组,但可以当做数组使用。

原因:C++与 C 一样,将数组名视为指针,指向第一个元素地址,对应元素大小为单个元素大小,详前。(相比之下,&a虽然也是指向首地址,但大小是整块数组的大小。)因而第一个形参实际是int* arr,即定义应当为:

int sum_arr(int* arr, int n);

这证明两种形参声明同时正确。在 C++中,int* arrint arr[]当且仅当出现在函数头或函数原型的时候,含义才相同。它们都意味着 arr 是一个int*

使用数组区间的函数

屁话一堆,无非就是给定首尾元素的指针为形参,在中间作妖罢了。不说也罢。

指针与 const

将 const 用于指针

  • 指针指向\\(const\\)常量,防止修改
  • 传参将指针本身声明为常量,防止修改指针指向的位置,但可以修改内容

优点:

  • 避免无意见修改数据导致错误
  • const可使函数能处理 const 与非 const 实参,否则只能接收后者。
//只能防止修改pt指向的值,但pt本身可以改成新地址
int age=39;
const int* pt=&age;
int sage=80;
pt=&sage;//是可以的,不会出问题

//下面两个是有区别滴,仔细看
int sloth=3;
const int* ps=&sloth;//指向const int类型的指针
int* const finger=&sloth;//指向int的const指针
//前者如上例,不能修改内容,但能重新指向
//后者则不能重新指向,但可以修改内容

二维数组作参数

int sum(int (*arr)[4], int size);
//等价于以下
int aum(int arr[][4], int size);

//不能声明为
int sum(int *arr[4], int size);

之所以不能声明为最后一个,是由于第一个形参应当是指向数组的指针,而不是单纯的二重指针。上述两个可用原型都指出,第一个形参类型实际上是指向 4 个元素的数组的指针。我之困惑于此也久矣,今乃得闻。

基于此,则传参之后仍可以二维数组待之,其原因为arr[i]被解释为*(arr+i),是第 i 个数组,则arr[i][j]被解释为第 i 个数组的第 j 个元素,即为二维数组。

返回 C 风格字符串

听起来很扯淡,对吧?返回值怎么可能是数组类型?内部声明的数组在退出的时候不是就释放了吗?

那么,有没有一种可能,我是说可能,内部用new申请一段空间给char*,然后写完了返回,在主函数中用完了在delete呢?

函数指针

为了实现函数指针,必须要完成:

  • 获取函数的首地址
    • 方法很简单,直接使用函数名即可。

think()是一个函数,则think就是该函数地址。要作为参数传递,必须传递函数名。要区分传递的是函数名还是函数返回值哦!

  • 声明一个函数指针
    • 声明一般指针需要说明指向的类型,函数指针也一样。这就是说,声明应指出函数的返回类型及其特征标(参数列表)。
  • 使用函数指针来调用函数

例程如下:

#include <iostream>
#include <string>
using namespace std;

bool hello(string name)

    cout << "Hello, " << name << endl;
    return true;


int main()

    // 函数指针
    bool (*pf)(string);
    // 函数参数为string型,返回值bool,指针名称pf。

    /* 上例是将<kbd>(*pf)</kbd>替代了<kbd>pam</kbd>,也就是说
     * <kbd>(*pf)</kbd>是函数
     *从而<kbd>pf</kbd>是函数指针。
     */

    /* 为提供正确的运算符优先级,应当用括号将`*`与`pf`括起来
     * 括号优先级高于`*`,从而:
     * <kbd>*pf(string)</kbd>意味着<kbd>*pf(string)</kbd>是返回指针的函数
     * <kbd>(*pf)(string)</kbd>意味着<kbd>pf</kbd>是指向函数的指针
     */

    // 赋值
    pf = hello;

    // 调用
    cout << pf("World") << endl;

我又要骂人了,这什么东西,类型声明这么复杂?不用了不用了,享受不来。

没事,C++11 还有个特性叫auto,不是么?

上边的例程可以被改写成如下形式:

#include <iostream>
#include <string>
using namespace std;

bool hello(string name)

    cout << "Hello, " << name << endl;
    return true;


int main()

    //函数指针
    auto pf = hello;
    // 调用
    cout << pf("World") << endl;

这自动推断类型,多是一件美逝了!让我们一起说,谢谢auto

另外,如果有若干函数返回类型和特征标都相同,都需要调用的话,何不考虑一下函数指针数组呢?更进一步地,为什么不选择创建一个指向整个函数指针数组的指针呢?

#include <iostream>
using namespace std;

// 没错,函数原型可以不写形参名称的
// 假设这段代码之外我们有函数的具体定义
const double *f1(int);
const double *f2(int);
const double *f3(int);

int main()

    // 定义一个指向函数的指针
    const double *(*p[3])(int) = f1, f2, f3;
    // 定义一个指向指针的指针
    auto pp = &p;

    // 直接调用函数指针数组
    cout << p[0](1) << endl;
    cout << p[1](2) << endl;
    cout << p[2](3) << endl;

    // 调用指向指针数组的指针
    cout << (*pp)[0](1) << endl;
    cout << (*pp)[1](2) << endl;
    cout << (*pp)[2](3) << endl;
    return 0;


const double *f1(int i)

    static double d = 1.1;
    return &d;


const double *f2(int i)

    static double d = 2.2;
    return &d;


const double *f3(int i)

    static double d = 3.3;
    return &d;

第八章

内联函数

内联函数是 C++为提高程序运行速度而做的一项改进。常规函数与内联函数区别不在于编写方式,而在于 C++编译器如何将它们组合到程序中。要了解它们的区别,必须深入到程序内部。

编译过程的最终产品是可执行程序,由一组机器语言指令组成。运行程序时,操作系统将指令载入内存,因而每条指令都有其特定的内存地址。计算机随后一一调用执行,或者有时候向前/向后跳转到特定地址,如循环、条件判断、分支语句等。

常规函数调用也是一个跳转,在此过程中,程序首先将跳转后需要执行的指令的地址压栈,并将现在的寄存器中参数复制到堆栈(为此保留的内存区),跳转到目标函数的地址,执行该函数,在遇到返回指令时,返回地址出栈,程序跳转到该地址,然后将参数复制回来,从而使原来的函数继续进行。

而内联函数的编译代码是与其他程序的代码“内联”的,换言之,编译器将使用内联函数的代码替换掉此处的函数调用,直接 copy 进来了。由于缺少了复制和跳转的过程,因而执行速度较快,但同时占据更多内存,调用几次该函数,该函数代码就被抄写几份。使用过程中需要综合考虑是否使用内联函数。

要使用内联函数,必须采取下列措施之一:

  • 在函数原型前加关键字inline
  • 在函数定义前加关键字inline

程序员做出内联请求时,编译器不一定同意(p.s.到底是我写代码还是编译器写代码?气抖冷!),它可能认为函数过大或者注意到有递归(众所周知,内敛这个形式不能递归的),或者有些编译器没有启用或实现内联这一功能。

#include <iostream>
using namespace std;

// 这是一个内联函数
inline double square(double x)

    return x * x;


int main()

    double a, b;
    double c = 13.0;

    a = square(5.0);
    b = square(4.5 + 7.5); // 可以传递表达式

    cout << "a = " << a << ", b = " << b << endl;
    cout << "c = " << c;
    cout << ", c squared = " << square(c++) << endl;
    cout << "Now c = " << c << endl;
    return 0;

输出结果为:

a = 25, b = 144

c = 13, c squared = 169

Now c = 14

输出表明,内联函数与普通函数一样,按值传递参数,参数为表达式则传递表达式的值。

C 语言中也可以像这样,不写函数原型,直接以函数定义充当函数原型。

内联的原始实现:C 中的#define

#include <stdio.h>
#define square(x) x*x

int main()

    double a, b, c = 3, d;
    a = square(5.0);
    b = square(4 + 5);
    d = square(c++);
    printf("a = %lf\\nb = %lf\\nd = %lf\\n", a, b, d);
    return 0;

这并不是通过传递参数实现,而是通过文本替换实现的——\\(x\\)是“参数”的符号标记。

上例只有\\(a\\)输出正确,可以用括号进行改进:

#define square(x) ((x) * (x))

即使如此,后两者依然输出错误,即无法实现按值传递。所以,使用内联函数应当尽可能考虑使用 C++的内联,而不是 C 的宏。

引用变量

引用变量是什么?能吃吗?

引用变量是已定义变量的一个别名。话不多说,上例程。

#include <iostream>
using namespace std;
int main()

    int n = 0;
    int &r = n;
    r = 1;
    cout << "n = " << n << endl;
    cout << "Addr r is " << &r << endl;
    cout << "Addr n is " << &n << endl;
    return 0;

运行结果如下:

n = 1
Addr r is 0x7ffce76ff48c
Addr n is 0x7ffce76ff48c

由结果可知,r 只是 n 的一个别名,在修改 r 的时候实质上就是在修改 n。二者的地址是相同的。

请注意:

  • 例程第 6 行的&并非取地址符,而是将 r 声明为一个int&型的变量;而在第 9/10 行的&则是取地址符。
  • 引用变量必须在声明的时候进行初始化,一旦初始化,即宣誓效忠,至死不渝,是无法改变的。换言之,int &r = nint* const *pr = n的一个封装表示而已。

引用变量作函数参数

在 C/C++一般的函数调用中,参数都是按值传递,即 copy 一份送到调用函数里使,即使做出了更改也不会直接影响原函数里的值,除非用返回值将其返回。有时候一个函数本来有自己需要返回的东西,又想把这个量修改了,就不好办了(用pair或者结构体,多少有点麻烦,还要为此创建一个类型)。这时候就可以考虑引用变量。

C-风格字符串作 string 对象引用参数

#include <iostream>
using namespace std;

const string &fuck(string &s1, const string &s2)

    s1 = s2 + s1 + s2;
    return s1;

int main()

    string s1 = "Hello";
    cout << fuck(s1, ", World!\\n") << endl;
    return 0;

程序可以接受将 C-风格字符串赋值给string&

首先,string类定义了char*string的转换,因而可以使用字符数组初始化字符串;

其次,类型为 const 引用的形参有一个属性:在形参与实参不匹配但可以转换的时候,程序会创建临时变量进行匹配,然后传参过去。换言之,程序调用之前创建一个临时string,把引用传递过去。

对象、继承和引用

使得能将特性从一个类传递给另一个类的语言特性称为继承。继承的另一个特性是,基类引用可以指向派生类对象而无需进行强制转换。这种特性的实际结果为,派生类中可以定义一个以基类引用为参数的函数,调用该函数时,既能以基类为参数,也可以派生类为参数。

ofstreamostream为例,前者为派生类,后者为基类,因为前者建立在后者基础之上。派生类继承了基类的方法,这意味着ofstream可以使用ostream的特性,如格式化方法和输出运算符;参数类型为ostream&的函数既可以接收ostream对象(如 cout),也可以接收已经声明的ofstream对象为参数。

何时使用引用

使用引用参数的原因:

  • 能修改调用函数的数据对象
  • 通过传递引用而非整个数据对象,提高运行速度。

当数据对象较大(如结构体、类对象),第二个因素占主要,这也是使用指针的原因,因为引用实际上是指针的另一个接口。

什么时候使用指针、什么时候使用按值传递呢?
对于使用传递的值而不作修改的:

  • 数据量小,按值传递
  • 是数组,指针传递,这是唯一办法,并将指针声明为指向 const 的指针
  • 数据对象是较大的结构体,使用 const 引用或 const 指针
  • 是类对象,使用 const 引用

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

  • 修改的是内置数据类型,使用指针
  • 是数组,就只能用指针
  • 是结构体,用指针或引用
  • 是类对象,用引用

函数多态

函数多态是 C++在 C 基础上增加的功能。

  • 默认参数允许以不同数目的参数调用同一函数
  • 重载允许使用多个同名的函数。

默认参数

默认参数指函数调用中省略实参时自动使用的实参值。

// 一个使用默认参数的例子
#include <iostream>
using namespace std;
int add(int a, int b = 1, int c = 2)

    return a + b + c;

int main()

    cout << add(5) << endl;       // 8
    cout << add(5, 6) << endl;    // 13
    cout << add(5, 6, 7) << endl; // 18
    return 0;

对于带参数列表(有形参)的函数,必须从右向左添加默认值,即所有有默认值的形参必须在所有无默认值形参的右边。使用时,传递的实参被从左到右依次赋给形参,而不得跳过任何形参。

函数重载

函数重载指可以有多个同名函数,它们以参数列表(形参,不包括返回值类型)为区别。C++使用上下文来确定要使用的重载函数版本。

//一个函数重载示例程序
#include <iostream>
using namespace std;

int add(int a, int b)

    return a + b;


double add(double a, double b)

    return a + b;


int main()

    cout << add(1, 2) << endl;
    cout << add(1.1, 2.2) << endl;
    return 0;

仅当在函数执行基本相同的任务而使用不同的数据类型时,才适合重载

重载虽好,可不要贪杯哦~

函数模板

函数模板是通用的函数描述,使用泛型来定义函数,其中的泛型可用于指定的具体类型(即int/double等)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。

少扯淡,一个代码就明白了

//一个使用函数模板的历次
#include <iostream>
using namespace std;

template <typename T>
T max5(T a[])

    T max = a[0];
    for (int i = 1; i < 5; i++)
        if (a[i] > max)
            max = a[i];
    return max;


int main()

    int a[5] = 1, 2, 3, 4, 5;
    double b[5] = 1.1, 2.2, 3.3, 4.4, 5.5;
    cout << max5(a) << endl;
    cout << max5(b) << endl;
    return 0;

第一行指出,要建立一个模板,类型命名为 T。关键字templatetypename是必须的,但可以使用关键字class替代typename>。另外,必须使用尖括号。类型名可以随意写。

函数模板本身并不创建任何函数,只是告知编译器如何定义函数,由编译器探明和处理,各自按照模板新建一个函数。因而,函数模板不能缩短可执行程序,在上例中最终仍然是两个独立函数,并无函数模板。但模板的好处在于使生成多个同类型函数更简单可靠。

常见用法为将模板放在头文件中,在需要使用的地方包含头文件

模板重载

模板满足了对不同类型使用同一种算法函数的需求,但并非所有类型都是用完全相同的算法。为满足此种需求,可以像重载常规函数一样重载模板定义,仍然需要满足参数列表不同便是了。

template<class T>//一个模板
void swap(T &a,T &b);

template<class T>//另一个模板
void swap(T* a,T* b,int n);

模板的局限性

模板局限性在于很可能无法处理某些数据类型。如赋值,一般类型赋值直接赋值即可,但数组则不可以;比较大小,一般数据类型可以直接比较,结构体则不行。

具体化与实例化

又是什么 JB 东西,似乎有用但似乎用处不大,先放着吧。

编译器选择哪个函数版本

对于函数重载、函数模板和函数模板重载,C++需要一个定义良好的解析策略来确认调用哪一个函数定义,尤其是有多个参数的时候。此过程称为重载解析。

  • 创建候选函数列表,包含所有与被调用函数同名的函数与模板
  • 使用候选函数列表,根据实参来创建可行函数列表
  • 确定是否有最佳的可行函数,有则使用,无则报错。优先级如下:
    • 完全匹配,但常规函数优先于模板
    • 提升转换(char/chort-->intfloat-->double
    • 标准转换(int-->charlong-->double
    • 用户定义的转换,如类声明中定义的转换。

完全匹配时,允许某些无关紧要的转换,如下表。

实参 形参
Type Type&
Type& Type
Type[] *Type
Type(argument0list) Type(*)(argument-list)
Type const Type
Type volatile Type
Type* const Type
Type* volatile Type
//调用函数
may(\'B\');

//所有同名
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

由上述规则可以看出,优先级情况如下:

  • 完全匹配:3、5、6
  • 提升转换:1
  • 标准转换:2
  • 不符合:4、7

其中,3、5 是常规函数,优先级高于 6 模板。

有两个最佳选择的情况一般认为是错误的,但有特例。

  • 指向非 const 数据的指针和引用优先与非 const 指针和引用参数匹配,反之然
  • 其中一个是非模板函数而另一个不是,此时废模板函数优先于模板函数(包括显式具体化)

C++ Primer Plus学习:第五章

C++入门第五章:循环和关系表达式

  1. for循环

for循环的组成部分

  1. 设置初始值。
  2. 执行测试,看循环是否应该继续执行。
  3. 执行循环操作。
  4. 更新用于测试的值。

以上操作由括号括起,每个部分均是一个表达式,彼此分号隔开,控制部分后面的语句叫循环体,若测试表达式均为true,它被执行。

for(initialization;test-expression;update-expression)

body

C++将整个for循环看成一个语句。

循环只进行一次初始化。

for循环和后面的括号加一个空格,以区别函数调用。

表达式和语句

10;就是一个表达式,值为10

C++规定赋值表达式的值为等号左边的部分。

如果加上分好,就变成一个语句。

修改规则

例:for (int i=1;i<5;i++) body

声明循环变量可在for循环中进行。

修改步长

i++也可变为i=2*i+1;i=i*I;

递增运算符(++)和递减运算符(++)

++x—x代表先加1,后使用。

x—x++代表先使用,后加1

注:这两个操作符在不同的系统上使用结果可能不同。

y=(4+x++)+(6+x++);在不同的系统中,有可能是逐步加1或统一加1

副作用和顺序点

副作用:计算表达式时对某些东西(如存储在变量中的值)进行修改。

顺序点是程序执行过程中的一个点,在这里,进入下一步之前将确保对所有副作用进行评估。C++中,分号就是一个顺序点,这意味着程序处理下一条语句之前将确保对所有的副作用都进行了评估。这意味着程序处理下一条语句之前,赋值运算符、递增运算符和递减运算符执行的所有操作都必须完成。

前缀格式和后缀格式

对内置类型,前缀和后缀表达式没有区别,但是对于用户定义的类型,往往前缀表带是比后缀表达式效率高。

组合运算符

操作符

作用(L为做操作数,R为右操作数)

+= 

L=L+R

-= 

L=L-R

*= 

L=L*R

/= 

L=L/R

%= 

L=L%R

复合语句(语句块)

可使用两个花括号来构造一条复合语句(代码块)。代码块由一对花括号和它们包含的语句组成,被视为一条语句。

逗号运算符

常用于for循环中的循环控制语句不止一个时。

逗号表达式的值是最后一个表达式的值。

关系运算符

操作符

含义

< 

小于

<= 

小于等于

== 

等于

> 

大于

>= 

大于或等于

!= 

不等于

字符串的比较

C-分隔字符串函数:strcmp(a,b);

ab之前,返回赋值,ab相同,返回0ab后,返回正值。

不可使用以下方式比较:

char name[20];

strcpy(name,"abc");

name=="abc"

但是可以这样:

string name;

name="abc";

name=="abc";

  1. while循环

while (test-condition)

body;

测试条件为真时,则执行,否则不执行。

设计循环的指导原则:

  • 指定循环终止的条件
  • 在首次测试之前初始化条件
  • 在条件再次测试之前更新条件

等待一段时间:编写延时循环

clock()函数,返回程序开始执行后所用的时间。

需载入头文件——ctime/time。这当中定义了一个符号常量——CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数。

程序示例:

#include<time>

……

clock_t(类型别名定义) start=clock();

while (clock()-start<delay)

;

类型别名

C++建立类型别名的方法有两种。

一种是使用预处理器:

#define BYTE char //预处理器将所有BYTE替换为char

第二种是使用C++关键字typeof来创建别名。

typeof char byte; //byte作为char的别名

typeof typeName aliasName;

预处理器是完全替代,typeof更智能。

#define F_P float *;

F_P pa,pb;等价于float * pa,pb。即只对于pa有效。但是typeof则不会有这样的问题。

  1. do-while循环

do

body

while(test-expression);

此循环为出口条件循环。

  1. 基于范围的for循环

C++11新增了一种循环:基于范围的for循环。这简化了一种常见的循环任务,对数组或者容器类的每个元素执行相同的操作。

例:double price={1.2,2.4,3.6,4.8,6.0};

for (double x:price)

cout<<x<<std::endl;

  1. 循环和文本输入

cin支持三种模式的单字符输入。

原始cin

如果程序要通过循环来读取来自键盘的文本输入,必须有办法知道何时停止读取。一种方法是选择某个某个特殊字符,我们称之为哨兵字符,将其作为停止标记。

例:

char ch;

int count=0;

cin>>ch;

while(ch!=‘#‘)

{

cout<<ch;

++count;

cin>>ch;

}

注意:上述程序会忽略空格和换行符。

使用cin.get(char)进行补救

char ch;

int count=0;

cin.get(ch);

while(ch!=‘#‘)

{

cout<<ch;

++count;

cin.get(ch);

}

文件尾操作

键盘模拟文件尾的快捷键:

UnixCtrl+D

WindowsCtrl+ZEnter

VC++Borland C++5.5GNU C++ 能够识别Ctrl+Z

通过cin的两个成员函数eof()fail()来检测文件尾,若为文件尾哦,即检测到EOFcin.eof()cin.fail()均返回true,否则返回false。这两个函数返回的是最近读取的结果。

则,上述程序变为:

char ch;

int count=0;

cin.get(ch);

while(cin.fail==false)

{

cout<<ch;

++count;

cin.get(ch);

}

使用cin.clear()可清除EOF标记。

常见的字符输入做法

常用:cin.get(ch);

while (cin.fail()==false)

{

………

cin.get(ch);

}

简单方式:

cin.get(ch);

while (!cin.fail())

{

………

cin.get(ch);

}

或者

cin.get(ch);

while (cin)

{

………

cin.get(ch);

}

还可以:

cin.get(ch);

while (cin.get(ch))

{

………

}

注:EOF不是输入中的值,而是指没有字符。

由于EOF不是有效的字符编码,所以不能与char类型相互兼容,所及先将返回值赋给int型变量,输出时再进行类型转换。

例:

int ch;

int count=0;

while((ch=cin.get())!=EOF)

{

cout.put(char(ch));

++count;

}

  1. 嵌套循环与二维数组

二维数组:

int a[3][3]; //三行三列的二维数组

以行为单位进行保存数据。

a[0]是有3个元素的数组。

初始化二维数组:

int a[3][3]=

{

{1,2,3},

{4,5,6},

{7,8,9}

},

 

以上是关于C++ Primer Plus基础知识部分快速通关的主要内容,如果未能解决你的问题,请参考以下文章

C++ Primer Plus学习:第一章

《C++ Primer Plus》学习笔记 第1章 预备知识

逆向基础 C++ Primer Plus 第三章 处理数据

C++ Primer Plus学习:第七章

C++ Primer Plus学习:第二章

C++ Primer Plus学习:第六章