Cpp Primer Plus Part2


  1. strcmp()接受两个字符串地址为参数,当第一个字符串的字母序小于第二个字符串时返回负数,若第一个大于第二个则返回正数,相等则返回0。不能简单地对C-风格字符串(用数组进行存储)用==来判断,因为编译器会将字符串解析为地址,即此时判断的是两个地址是否一致而非比较内容是否一致;而当至少一个操作数是string类对象时,则可以直接进行比较。

  2. 计时函数:clock(), 位于<ctime>头文件中,且其返回类型为clock_t类型,因而变量可以定义为clock_t,而编译器会将其转换为long、unsigned int或适合系统的其他类型。注意,clock()返回的实际上不是以秒为单位的时间,而是以系统时间单位数为单位的,因而需要将获取到的时间除以CLOCK_PRE_SEC来转换为秒。

#include <iostream>
#include <ctime>
using namespace std;
int main(void) {
    cout << "Enter the delay time, in seconds:" << endl;
    float secs;
    cin >> secs;
    secs *= CLOCK_PER_SEC;
    clock_t start = clock();
    while(clock()-start < secs);
    cout << "done!" << endl;
    return 0;
}
  1. cin会忽略空格和换行符, 如果需要读取换行符或空格,则应该采用无参的cin.get()。设置哨兵字符用于判断是否到达输入流的最后一个字符并不能令人满意,可以采用EOF来检测。当检测到EOF时,cin.eof()cin.fail()都将将会返回true,通过调用这两个函数即可知道当前是否到达EOF。
#include<iostream>
using namespace std;
int main(void) {
    char ch;
    cin.get(ch);
    while(!cin.fail()) {    // !cin.eof()
        cout.put(ch);       // linux中eof为ctrl+D,Windows则为ctrl+Z
        ch = cin.get();
    }
    return 0;
}

实际上,cin.get(char)返回的是一个cin对象,因而也可以用while(cin)来判断是否读取成功。

  1. 字符函数库cctype(ctype.h)
  • isalnum(ch): 如果ch为字母或数字则返回true;
  • isalpha(ch): 如果ch为字母,则返回true;
  • ispunct(ch): 如果ch为标点符号则返回true;
  • isdigits(ch): 如果ch为数字则返回true;
  • isspace(ch): 如果ch为空格则返回true;
  • iscntrl(ch): 如果ch为控制字符则返回true;
  • isgraph(ch): 如果ch为除空格外的打印字符则返回true;
  • islower(ch): 如果ch为小写字母则返回true;
  • isprint(ch): 如果ch为打印字符(包括空格)则返回true;
  • isupper(ch): 如果ch为大写字母则返回true;
  • toupper(ch): 如果ch为小写字母则返回其大写,否则返回其本身;
  • tolower(ch): 如果ch为大写字母则返回其小写,否则返回其本身;
  • isxdigit(ch): 如果ch为十六进制数字,即0-9、a-f或A-F,则返回true.
  1. 文本文件操作:
  • 写入操作:头文件为<fstream>,包括文件输出类ofstream. 将文件和ofstream对象关联起来的方法为open(),关闭为close(). 与iostream不一样的是文本文件需要自己声明ofstream对象,而iostream则提供了一个预先定义coutostream对象。

如果目标路径不存在open参数指向的文件时,将会新建一个同名文件;而当已存在该文件时,则将默认对该文件进行截断,即将其长度截短到零————丢弃其原有内容,并重新输入内容到该文件。

  • 读取操作:头文件为<fstream>,包括文件输入类ifstream. 将ifstream与文件关联的方法同样为open()。类似cin,可采用get()来读取单个字符,而getline()来读取一行字符。判断文件是否成功打开可使用is_open()

读取文件时,通过EOF判断是否达到文件尾(即文件读取完成),即eof()返回true;而当文件中的内容与变量类型不匹配时,fail()将会返回true;而当文件打开失败时bad()会返回true. 可以直接通过方法good()来排查这些情况。

  1. Cpp对于返回值的类型有一定的限制:不能是数组,但数组可以作为结构或对象的成员来返回。

  2. 函数原型描述了函数到编译器的接口,他将函数返回值类型、参数的类型和数量告诉编译器。函数原型不要求提供变量名,有类型列表即可。当参数列表为空,对C来说等同于void,即无形参列表;而对于Cpp来说,则视为没指出参数,因而后续需要定义参数列表。

  3. argument表示实参,是传递给函数的变量数值;而parameter则是形参,指函数中用于接收传递至的变量。

  4. 用const修饰的变量,意味着程序不应该试图对该变量进行修改。类似的,const int *pa意味着pa指向的是一个const修饰的变量,因而无法通过pa去修改该变量;而int* const pa则意味pa本身是常量,即无法修改其指向。const修饰的变量的地址不可以传递给非指向const的指针,否则就可以通过指针修改const变量了;而非const变量可以传递给指向const的指针,只是无法通过指针修改该变量而已。

  5. 数组指针和指针数组:

  • 数组指针:指向一个数组的指针,定义为int (*arr)[n];, 其意义为该指针指向了一个具有n个int元素的数组,访问时应该通过(*arr)[i]来访问。
  • 指针数组:数组内容为指针,定义为int* arr[n];,访问时通过*(arr[i])*(arr+i)来访问。
  1. 函数无法返回一个字符串,但可以返回字符串的首元素的地址,这意味着所返回的字符串数组应该是通过malloc或者new申请的。

  2. 可以通过数组名获得其第一个元素的地址,它同时也是整个数组的地址;而对于结构体或者类而言,则需要取地址运算符&来获取其地址。

  3. 函数指针:每个函数都有一个入口地址,当调用某个函数时,实际上是主程序移动到该地址去执行该函数的函数体,而函数指针就是指向这个地址的指针变量(函数地址通过函数名来获取不加参数列表和括号,单纯函数名即可)。

      对于函数原型:double fcn(int);,其函数指针应该定义为:double (*pf)(int);.注意,必须将*pf用括号括起来,否则该语句的含义就变成一个返回值类型为double *、具有一个int参数的函数了。前者是函数指针,后者是指针函数。

    int fcn1(int param1);
    double fcn2(double param2);
    int fcn3(double param3);
    int (*pf)(double);
    pf = fcn1;  // invalid
    pf = fcn2;  // invalid
    pf = fcn3;  // valid
    int a = pf(1.0);        // correct
    int b = (*pf)(1.0);     // correct
    
    int fcn1(double param1);
    int fcn2(double param2);
    int fcn3(double param3);
    int (*pf1[3])(double) = {fcn1, fcn2, fcn3}; // 注意[3]的位置

为什么[3]的位置在那里,首先*的优先级低于[],因而*pf[3]是一个具有3个指针的数组,其实就是指针数组。auto只能用于单值初始化,而不能用于初始化列表
通过typedef关键字为指针函数取别名:typedef int (*p_fcn)(double);,此后就可以通过p_fun来定义函数指针:p_fun pa = fcn1;.

  1. 当函数执行到调用函数指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数赋值到堆栈,跳到标记函数起点的内存单元执行调用的函数代码,然后跳回地址被保存的指令处。来回跳跃并记录位置意味着函数调用需要一定的开销。而内联函数则通过扩充代码量来避免这一开销,即将经常调用的函数体直接复制到调用的地方,是一个典型的空间换时间的例子。
    内联函数的要求是不存在复杂的命令、函数体简单且不应该调用自身(递归)。
    内联函数和宏函数都是通过将对应的代码复制到“函数”调用的地方来节省函数调用引起的开销,而宏函数只是简单的字符替换不进行类型的检测,而内联函数具有函数的性质,因而有类型的检测。

  2. 引用是给已定义的变量的取别名,其主要用途是用于函数的形参,此时函数内使用的变量就是原始变量而不需要额外创建一个副本,使得效率更高。引用和指针的区别是引用定义时就必须进行初始化且一旦初始化后就不能修改,也就是说引用有点类似常量;而一般的指针都是可以修改指向的地址。
    由于调用函数可以对引用变量进行修改,因而当仅需要用到该变量的值时,可以在前边加上const以防止程序对其进行修改。
    对于类型不符合的引用,前提是函数中的引用变量是用const进行修饰,则此时会创建临时变量,并将该变量传递给函数,此时类似于值传递。
    C++11中允许对右值进行引用,其形式为double &&ref = sqrt(36.00;,即需要两个引用符号。(右值指的是字面量)

  3. 当函数返回的是对象的引用是,此时只会发生一次拷贝,即将引用对象的内容拷贝到目标内存中;而当返回的是对象时,则会发生两次拷贝,第一次发生在函数结束调用时,会将结果进行拷贝并将其进行返回,第二次则是将返回的结果的内容拷贝到目标内存中。注意,应该避免函数返回一个不再存在的内存单元引用,也应该避免返回指向不再存在的变量的指针。

  4. cout中的setf()方法允许设置各种格式化状态:

  • setf(ios_base::fixed): 将对象置于使用定点表示法的模式,比如对浮点数则是按一般形式进行输出。
  • setf(ios_base::showpoint): 显示小数点后的0,即当小数点后的位数小于精度值是,自动补0

    cout中的precision()则用于设置显示小数位数,width()用于设置所要显示的宽度(默认为0,即恰好容纳目标长度)。

  1. 传递方式选择:
  • 不需要修改值:
    • 数据对象很小,使用值传递;
    • 对象是数组,只能采用指针传递;
    • 对象是较大的结构,采用const指针或const引用;
    • 对象是类对象,采用const引用(标准)。
  • 需要修改值:
    • 对象为内置的数据类型,则用指针;
    • 对象为数组,只能用指针;
    • 对象是结构,使用指针或引用;
    • 对象是类对象,采用引用。
  1. 带参数列表的函数,参数默认值只能从右到左添加,即参数列表中某个参数若有默认参数,则其右边的参数都要提供默认值,而默认参数只在原型中指定,定义与无默认参数的一样。

  2. 函数重载是为了实现同个函数名具有不同函数体,即函数多态。函数重载的关键是函数的参数列表即函数特征标不同。如果两个函数函数名相同,而函数的参数列表个数/参数列表类型/返回类型不同(只有返回类型不同时不视为重载),则两个函数的特征标不同。
    重载的实现是通过 “名称修饰/名称矫正” 来实现的。

  3. 函数模板是通用的函数描述,其使用泛型来定义函数,而泛型可以用具体的类型来替换,通过将类型作为参数传递给模板,编译器则可以生成该类型的相关函数。实际上,函数模板并未创建任何函数,而是告诉编译器如何定义函数。

    template <typename T>   // template <class T>
    void fcn(T x) {
        ...;
    }

    作用:同一种算法应用于不同参数类型,且模板参数不一定都是泛型。注意,使用模板并不能缩短可执行程序,因为编译器会根据需求生成对应的函数,其实就跟手动定义了该函数一样,仅仅是便于开发。

    类似函数重载,模板也可以进行重载:

    template <typename T>   // original template
    void swap(T & a, T &b) {
        T tmp = a;
        a = b;
        b = tmp;
    }
    
    template <typename T>   // overload template
    void swap(T *a, T *b, int n) {
        for(int i=0; i<n; ++i) {
            T tmp = a[i];
            a[i] = b[i];
            b[i] = tmp;
        }
    }

    针对特定的结构体,如果只想交换其中部分成员变量的值,那么直接采用函数模板则无法实现目标,因而还有显式具体化模板函数。非模板函数优先级高于显式具体化模板函数高于模板函数。显式具体化模板函数必须以template <>为开头:

    void swap(job& a, job& b);  // 非模板
    
    template <typename T>       // 模板
    void swap(T &a, T &b);
    
    template <> void swap<job> (job& a, job& b);    // 显式具体化模板,swap后的<job>可以省略

    实际上,模板本身不会产生函数定义,而模板实例才是函数定义(模板实例就是传入一个类型给模板,此时就是创建了一个实例,而实例本身就是函数)。通过传入类型的方式实际上采用的是隐式实例化,而显式实例化为:

    template void swap<int> (int, int);

    而与显式实例化相近的还有显式具体化(两种写法等价):

    template <> void swap<int>(int &, int &);
    template <> void swap(int&, int &);

    隐式实例化、显式实例化、显式具体化统称为具体化,相同之处在于都是使用具体的类型的函数定义而不是通用描述。显式实例化和显式具体化的区别在于前者是通过模板来生成对应类型参数的代码,而后者则是“不使用模板来生成实例,而是专门为对应类型显式地定义函数”。前者以template开头,而后者则是template <>开头。

  4. 重载解析

  • 创建候选函数列表(包括与调用函数同名地函数以及模板函数)
    • 完全匹配(非模板函数优先级高于模板函数): 如果有多个函数可行,则指向非const指针或引用优先和非const指针或引用进行匹配,但这种只适用于指针和引用类型,对于数值类型不起作用。
    • 提升匹配(整型提升、浮点数提升)
    • 标准匹配
    • 自定义转换
  • 通过候选函数列表创建可行函数列表
  • 确定最佳的可行函数

    void recycle(blot);         //#1
    void recycle(const blot);   //#2
    void recycle(blot &);       //#3
    void recycle(const blot &); //#4
    template <typename T> void recycle(T &t);   //#5,模板函数
    template void recycle<blot>(blot &t);       //#6,显式实例化
    template <> void recycle<blot>(blot &t);    //#7,显式具体化
    
    struct blot{
        int a;
        char b[10];
    };
    blot ink = {25, "spots};
    recycle(ink);
    /*1和2无法比较,且会与3,4导致二义性,3>4>7>6>5*/
    /*同为完全匹配,更具体的优先级越高*/

    总之,重载解析将寻找最匹配的函数。如果只存在这样一个函数,那么就直接选择它;如果存在多个这样的函数,但其中只有一个非模板函数,则选择该非模板函数;如果存在多个都属于模板函数的函数,那么选择其中最具体的那个函数。如果有多个同样何时的非模板函数或模板函数,但没有一个更为具体,则报错。

  1. decltype(expression) var;: 定义一个var变量,其类型与expreesion的类型一致。如果expression为未被括号括起的变量,则var的类型就是该变量的类型;如果expression为一个函数,则var的类型为该函数的返回值类型(并不会调用该函数);如果expression为被括号括起的变量,则var类型为该变量类型的引用;前三种情况不满足,则var类型等同于expression类型。
long inded(int);
double x = 5.4;
double& y = x;
const double *pd;
decltype(x) w;          // 1.type double
decltype(y) u = y;      // 1.type double &
decltype(pd) v;         // 1.type const double *
decltype((x)) r;        // 3.type double &
decltype(indeed(3)) m;  // 2.type long
int j =3;
int &k = j;
int &n = j;
decltype(j+6) i1;       // 4.type int
decltype(100L) i2;      // 4.type long
decltype(k+n);          // 4.type int

template <typename T>
void fcn(T &a, T &b) {
    ...
    typedef decltype(a+b) xytype;   // 取别名
    xytype xpy = x+y;
    ...
}

template <typename T>
auto fcn(T &a, T &b) -> decltype(a+b) { // 返回类型后边指定为decltype(x+y)
    ...;
    return x+y;
}

文章作者: Vyron Su
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vyron Su !