C/C++教程

C++ Primer笔记--Part2 C++基础

本文主要是介绍C++ Primer笔记--Part2 C++基础,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

  C++类型检查发生在编译时。

第二章 变量和基本数据类型

2.1 内置数据类型

long long是在C++11中新定义的

字符型分为三种:char、signed char和unsigned char

如果需要使用一个不大的整数,明确指定它的类型是signed char或者unsigned char

浮点运算一般选用double,因为计算代价相差无几,甚至某些机器double比float快

当我们给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数;

当给一个带符号类型一个超出它表示范围的值,结果是未定义的。(指位数超出)

当从无符号数减去一个值时,不管这个数是不是无符号,最终结果不能是负数;

unsigned int u1 = 42, u2 = 10;        
std::cout << u2 - u1 << std::endl;  
//正确,输出-32的模(即-32+unsigned int的最大值,如果是32bit,则结果为4294967264),即结果取模

以0开头表示八进制,以0x/0X开头表示十六进制

默认浮点型字面值是一个double

2.2 变量

2.2.1 变量定义

double price = 109.99, discount = price*0.16; 
//正确,price先被定义并赋值了,随后才用于初始化discount
四种初始化风格:
    int units_sold = 0;
    int units_sold = {0};
    int units_sold {0};
    int units_sold (0);

使用花括号初始化是C++11标准的一部分,也称为列表初始化。当用于内置类型的变量时,这种初始化形式有一个重要的特点:如果使用列表初始化且初始值存在丢失信息的风险,编译将报错。

double d = 3.14;
int i= {d}; //报错,会丢失信息
int i= d;//可以通过,但会警告

默认值初始化:定义在函数体内部的内置类型变量将不被默认值初始化。

初始化和赋值是两个概念,尽管有时候区别不大。

2.2.2 变量声明和定义

如果想声明一个变量而非定义它,在变量名前添加关键字extern,且不要显式初始化变量。

extern int i;//只声明
int j;//声明并定义

尽管我们可以显示初始化由extern关键字标记的变量,但是,在函数体内,如果试图初始化一个由extern关键字标记的变量,将引发错误。

多文件编写中,一个变量只能在一个文件中定义,其他用到该变量的文件只能声明,却绝对不能重复定义。

约定熟成的变量命名规范:不能使用C++的关键字,不能使用为标准库保留的标识符

 

 

 

2.2.4 作用域

C++中大多数作用域都以花括号分隔。

::reused;//表示全局作用域下的reused

2.3.1 引用

C++11新增的一种引用:所谓“右值引用”,这种引用主要用于内置类。我们一般讲的引用是“左值引用”。

在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。

引用只能绑定在同一类型的对象上,而不能与字面值或某个表达式的计算结果绑定在一起,除非是常量引用或指向派生类的基类型引用。

2.3.2 指针

除了指向非常量对象的指向常量的指针(即一个指向常量的指针指向非常量)和指向派生类的基类指针,指针的类型必须和它指向的对象严格匹配。

nullptr:C++11新标准,特殊类型的字面值,用于指针的空值。

任何非零指针对应的条件值都是true。

void* 是一种特殊指针类型:不能直接操作void* 指针所指的对象

判断指针指向是否合法:

  • 把if(p)置于try结构中,当程序块顺利执行,表示p指向了合法的对象;当程序块出错跳转到catch语句时,表示p没有指向合法的对象。

指针和引用的主要区别

指针本身是一个对象,引用不是。

指针无须在定义时赋值,而引用必须在定义时赋初值。

2.3.3 复合类型

 类型修饰符*和&修饰的是变量名而不是数据类型

//指向指针的引用:
int *p;
int * & r = p;//r是一个对指针p的引用

离变量名最近的符号对变量的类型有最直接的影响,因此 int* *&r,r是引用,*说明r引用的是一个指针。

2.4 const限定符

编译器在编译过程中将用到const变量的地方替换成对应的值。

默认情况下,const对象被设定为仅在本文件内有效;若想在不同文件使用同一个const对象,则定义const对象时必须添加extern关键字。

常量引用:指向常量的引用(引用本身不是对象,事实上并不存在常量引用)。

允许为一个常量引用绑定非常量的对象。

顶层const:指const是作用于对象本身的,常量指针就是一种顶层const

底层const:指向常量的指针

2.4.4 constexpr和常量表达式

常量表达式:指值不会改变并且在编译过程就能得到计算结果的表达式

constexpr关键字:C++11标准,声明为constexpr的变量一定是一个常量,并且由常量表达式初始化(编译器验证)

指针和constexpr

constexpr把它所定义的对象值置为顶层const,即constexpr修饰的指针为常量指针

constexpr int * i = &j;
const int * p = &j;
//p是指向常量的指针,而i是常量指针。且i指向的j必须定义在函数体外,因为函数体内的临时变量的地址非固定

2.5 处理类型

2.5.1 类型别名

typedef关键字

typedef double wages, *p; //wages是double的同义词,p是double * 同义词
using SI = Sale__item; //新标准    using 别名 = 类型名

指针、常量和类型别名

typedef char  *pstring;
const pstring cstr = 0;
//注意:cstr是指向char的常量指针//而不是指向常量的指针

2.5.2 auto类型说明符

C++11标准,auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值。

auto可以在一条语句中声明多个变量,但所有变量的基本数据类型应该相同。

auto一般会忽略顶层const,同时底层const会保留下来;如果希望推断出的auto类型是一个顶层const,需要明确指出:const auto

引用也可以用auto:

auto &g = ci;//正确
auto &h = 42;
//错误,因为auto会忽略顶层const,所以不能转换成指向常量的引用,除非(见下例)
const auto & j = 42;//正确,

2.5.3 decltype类型指示符

decltype:C++11标准,返回操作数的数据类型,编译器分析表达式并得到它的类型,却不实际计算表达式的值

decltype(f()) sum = x; //sum的类型就是函数f的返回值类型,f没有被调用

decltype使用的是变量,则返回变量的类型(包括顶层const和引用在内)

decltype使用的是表达式,则返回表达式结果对应的类型;如果表达式的内容是解引用操作,则decltype将得到引用类型。

 

对于decltype所用的表达式来说,变量名加上一对括号,则解释为表达式,且变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型;同时表达式(i = x)的类型是int&

2.6 自定义数据结构

2.6.1 数据结构的定义

C++11允许为数据成员提供一个类内初始值,可以放在花括号里,或者放在等号右边,不可以放在圆括号。

分号作为声明符的结束,所以数据结构、类定义,要以分号为结束。

2.6.3 头文件

预处理器:确保头文件多次包含仍能安全工作的常用技术是预处理器。#define、#ifdef、#ifndef、#endif

inline函数可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。如inline函数func()的形参为类A,则可以在func()函数内部使用类A的保护成员和私有成员。

 

第三章 字符串、向量和数组

3.1 using

using std::cin;

3.2 string

#include <string>
using std::string;

3.2.1 定义和初始化

string s1; //默认初始化,s1是空字符串
string s4(10,‘c’); //s4的内容是cccccccccc

3.2.2 string对象上的操作

 

getline(is,s)函数会一直读取输入流,直到遇到终止符(默认为‘\n’),终止符会读出并舍弃掉。

getline会返回它的流参数,所以也可以将getline的结果作为条件实现读入行数未知的数据。

string::size_type是一个无符号类型的值,可以存放下任何string对象的大小。

char*、char[]类型的对象是不能用==进行比较的,得使用strcmp()函数,相等返回0。

3.2.3 处理string对象中的字符

 

C++兼容C语言的标准库。C语言的头文件形如name.h,C++则将这个文件定义为cname,也就是去掉了.h的后缀。因此,cctype头文件和ctype.h头文件的内容是一致的,但是从命名规范上更复合C++的语法的要求。

范围for语句

C++11标准,遍历给定序列中的每个元素并对每个值执行某种操作,语法如下:

for(declaration:expression)
    statement

auto和范围for的结合特别好用

用decltype定义与字符串相关的计数变量:

decltype(s.size()) cnt = 0;

修改字符串中单个字符:

  • 使用swap()函数
  • 对于字符串的单个字符使用引用,通过引用修改
  • 通过str[i]直接修改

3.3 vector

C++的vector能在运行是高效快速地添加元素,vector对象能高速增长。因此,除非所有(all)元素的值都一样,不然在运行时动态添加元素的值会更高效。

而C或者Java则不同,在已知容量的情况下,先申请内存空间更高效。

如果在循环体内部含有向vector对象添加元素的语句,则不能使用范围for(范围for语句体内不应修改其遍历序列的大小)

 

3.4 迭代器

cbegin()和cend()返回const_iterator类型对象(C++11标准)

箭头运算符(->)把解引用和成员访问两个操作结合在一起

任何一种可能改变容器对象容量的操作,都会使该对象的迭代器失效

C++没有定义两个迭代器的加法运算,因为没有意义。C++定义了迭代器之间的减法运算,结果是它们之间的距离。

3.5 数组

用字符串字面值给数组赋值,数组的维度应该比字面值长度+1,储存 ' \0 '。

 

 默认情况下,类型修饰符从右向左依次绑定

 

因为string类型本身接收无参数的初始化方式,所以不论数组定义在函数内还是函数外部都默认被初始化为空串。

对于内置int来说,数组ia定义在所有函数体外,根据C++的规定,ia的所有元素值为0;而数组ia2定义于main函数内部,将不被默认值初始化。

unsigned scores[11] = {}; //可以用这种方式在函数体内实现全部元素初始化

对于字符数组,可以调用strlen函数得到字符串的长度;而其他数组,只能使用sizeof(array)/sizeof(array[0])计算数组的维度。

 当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组。

 

 注:使用decltype时,上述转换不会发送,会得到数组类型

 

得到数组尾元素的下一位置的指针

int *e = &arr[10];//arr是维度为10的数组,arr[10]是尾元素的下一位置,所以e不能解引用或递增操作

标准库函数begin和end

C++11标准引入库函数begin()和end()

int *beg = begin(ia);//ia是一个数组名,end()用法相同

内置的下标运算符的索引值不是无符号类型,与vector和string不一样。即数组的下标运算符内可以填负数,但结果地址必须指向原本的指针所指向同一数组中的元素。

当使用数组的时候其实真正用的是指向数组首元素的指针。

3.5.4 C风格字符串

 

 传入此类函数的指针必须指向以空字符为结束的数组。

允许使用空字符结束的字符数组与string对象进行各种运算,

 即C风格字符串->string,但是反过来不能转换成功,不过string为此提供了c_str方法,但该方法返回的字符数组不能保证一直有效,如果想一直使用最好拷贝一份。

允许使用数组初始化vector对象:

 

 范围for访问多维数组:

 

当范围for访问多维数组时,除最内层的循环外,其他所有循环的控制变量都应该是引用类型(不然编译器会auto为指针类型)

 

第四章 表达式

4.1 基础

4.1.1 基本概念

decltype作用于左值表达式得到的是一个引用类型,作用于右值表达式得到的是值的类型。

4.1.2 优先级和结合律

算术运算符满足左结合律,即如果运算符优先级相同,将按照从左向右的顺序结合对象。

IO相关的运算符满足左结合律。

成员选择运算符(.)和函数调用运算符比解引用运算符的优先级高

4.1.3 求值顺序

大多数运算符没有规定求值顺序,除了逻辑与(&&)运算符、逻辑非(||)运算符、条件(?:)运算符、逗号(,)运算符。

故,以下语句可能产生错误:

int i = 0;
cout << i << " " << ++i << endl;   
//可能输出0 1,也可能输出 1 1,因为没有规定i和++i的求值顺序

4.2 算数运算符

 

取余运算,m%n,结果的符号与m相同

4.3 逻辑和关系运算符

 

const char *p = “Hello World”;
if(p && *p)   //先检查指针p是否为空,再检查 *p指向是是否为空字符‘\0’
while(cin >> num && num != 42);//接收标准输入直到接收到42

4.4 赋值运算符

赋值运算符满足右结合律

复合赋值运算符

对于左侧运算对象,复合赋值运算符只求值一次,而普通运算符则求值两次。

4.5 递增递减运算符

后置运算符会保存修改前的运算对象的值,如非必要,否则不用递增递减运算符的后置版本。

后置自增(自减)运算符的优先级高于前置自增(自减)运算符高于解引用运算符。

用 *it++;代替 *it;++it;

4.6 成员访问运算符

点运算符和箭头运算符的优先级高于解引用运算符

4.7 条件运算符

条件运算符满足右结合律,两个结果表达式的类型必须相同或可以相互转化。

嵌套条件运算符

finalgrade = (grade > 90) ?  "high pass"
                                        : (grade < 60) ? "fail" : "pass"  
string p1 = s + s[s.size()-1] == 's' ? "" : "s"; 
//该语句编译失败,根据运算优先级,先加法运算,然后相等运算,然后条件运算、 最后赋值运算。
//相等运算比较字符串和字符是非法操作。
//可以改写为:string p1 = s + (s[s.size()-1] == 's' ? "" : "s"); 

4.8 位运算符

位运算符用于整数类型和bitset类型(标准库类型,可以表示任意大小的二进制集合)的的运算对象。

 

 位运算符关于符号位如何处理没有明确规定,所以强烈建议仅将位运算符用于处理无符号类型。

如果运算对象是“小整形”,则它的值会被自动提升(参见4.11.1节)。

左移运算符(<<)在右侧插入值为0的二进制位。右移运算符(>>)的行为则依赖其左侧运算对象的类型:如果该运算对象是无符号类型,在左侧插入值为0的二进制位;如果该运算对象是带符号类型,在左侧插入0还是符号位的二进制位则取决于具体环境。

4.9 sizeof运算符

sizeof满足右结合律。

sizeof返回常量表达式,size_t类型。

两种运算对象写法:

  • sizeof(type)
  • sizeof expr

sizeof返回表达式结果类型的大小,但并不实际计算表达式。

sizeof不会把数组转换成指针来处理。

对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小。

 sizeof的表达式中的指针解引用不需要有效(即在sizeof中解引用一个无效的指针不会报错,因为不会执行解引用操作)

4.10 逗号表达式

规定了运算对象求值顺序,按照从左到右的顺序依次求值。

逗号运算符真正的结果是,右侧表达式的值。

someValue? ++x,++y :--x,--y;
//等价于 (someValue? ++x,++y :--x),--y;  ,因为条件运算符比逗号运算符优先级高

4.11 类型转换

4.11.3 显示转换

命名的强制类型转换:

  • static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast
  • dynamic_cast
  • const_cast:只能改变运算对象的底层const
  • reinterpret_cast

4.12 运算符优先级表

见C++ Primer书本147页

 

第五章 语句

5.3 条件语句

switch语句,计算一个整型表达式的值,然后根据这个值从几条执行路径中选中。case标签必须是整型常量表达式。

如果要为某个case标签定义变量,需要将该case区域用花括号括起来,保证变量只作用在此作用域内。

如果case标签要使用变量,则应使变量成为整型常量表达式,即定义为const。

5.4 迭代语句

定义在while条件部分或者while循环体内的变量每次迭代都经历从创建到销毁的过程。

出现在do-while的条件部分的变量必须定义在循环体之外

5.6 try语句块和异常处理

throw表达式

try语句块:以一个或多个catch子句结束

异常类

5.6.3 标准异常

只能以默认初始化的方式初始化exception、bad_alloc和bad_case对象,其他异常类型则恰恰相反,创建时必须使用string或者C风格字符串初始化这些类型的对象。

异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串的const char *。

 

第六章 函数

6.2.3 const形参和实参

当用实参初始化形参时会忽略形参的顶层const

 

不能使用const实参去初始化非const 的引用形参,但是可以用非const实参去初始化const引用形参。

如果形参是值且不需要修改的,尽量使用常量引用

6.2.4 数组形参

数组的两个特殊性质:不允许拷贝数组,使用数组时(通常)会将其转换成指针。

void print(const int *);
void print(const int [ ]);  //
void print(const int [10]); //这里的维度表示我们期望数组含有多少元素,实际不一定

上面三个函数是等价的,传入的参数类型都是const int *

数组的引用

int (&arr)[10] //维度为10的数组的引用

传递多维数组

void print(int (*matrix)[10], int rowSize);
void print(int matrix[] [10]);
//上述两种等价

6.2.5 main:处理命令行选项

如果可执行文件prog,main函数位于此文件内

prog -d -o ofile data0

int main(int argc, char*argv[]) {  }

以上面提供的命令行为例,argc等于5,argv应该包含如下C风格字符串

argv[0] = "prog"; //或者argv[0] 也可以指向一个空字符串
argv[1] = "-d";
argv[2] = "-o";
argv[3] = "ofile";
argv[4] = "data0";
argv[5] = 0;    //最后一个指针之后的元素值保证为0

6.2.6 含有可变形参的函数

initializer_list的标准库类型

可变参数模板

C++的特殊形参类型(即省略符),一般用于与C函数交互的接口程序

initializer_list形参

 

 initializer_list对象中的元素永远是常量值,我们无法改变其元素的值。

省略符形参

省略符形参应该仅用于C和C++通用的类型。特别应该注意的是,大多数类型的对象在传递给省略符形参时都无法正确拷贝。

省略符形参只能出现在形参列表的最后一个位置。

cstdlib的预处理变量

return EXIT_FAILURE表示main执行失败

return EXIT_SUCCESS表示main执行成功

6.3.3 返回数组指针

声明一个返回数组指针的函数

Type (*function(parameter_list))[dimension]

Type表示数组元素的类型,dimension表示返回的数组的大小,(*function(parameter_list))两端的括号不能省略,表示返回的是数组指针

【注】注意区分返回值是数组指针(即返回值是数组),还是指针的数组(即返回的数组的元素是指针)

可以按照以下的顺序来逐层理解该声明的含义:

  • func(int i)表示调用func函数时需要一个int类型的实参。
  • (*func(int i))意味着我们可以对函数嗲用的结果解引用操作
  • (*func(int i))[10]表示解引用func的调用将得到以哦个大小是10的数组。
  • int (*func(int i))[10]表示数组中的元素是int类型

使用尾置返回类型

C++11标准

auto func(int i)-> int(*)[10];
//表示返回一个指针,指向含有10个整数的数组

使用decltype关键字声明返回值类型

int odd[ ] = {1,3,5,7,9};    
decltype(odd)*arrPtr(int i);
//decltype并不负责将数组类型转换成对应的指针,所以表示返回值为数组指针,要在后面加上 *

使用类型别名

typedef string arr[10];
using arr = string[10];  
//这两条语句都是声明一个含有10个string元素的数组的类型别名
//如果想表示数组的引用,一般 using arr = string[10]; arr&

6.4 函数重载

函数重载:同一作用域内,函数名字相同但是形参列表不同。

顶层const不影响传入函数的对象,故一个顶层const的形参无法和另一个没有顶层const的形参区分开来。

如果形参是某种类型的指针或引用,则通过区别其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的。(当我们传递非常量对象或指向非常量对象的指针时,编译器会优先选择非常量版本的函数(如果存在))

6.5 特殊用途语言特效

6.5.1 默认实参

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

一个形参只能被赋予一次默认实参(即给定作用域中,多次声明中,一个形参只能出现一次默认实参)。

6.5.2 内联函数和constexpr函数

内联函数和constexpr函数通常定义在头文件中。

内联函数

内联函数可避免函数调用的开销(但是会增加代码文件的大小)。

内联函数声明关键字 inline。

内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。

constexpr函数

constexpr函数指能用于常量表达式的函数。遵循几项约定:

  • 函数的返回值类型及所有形参的类型都得是字面值类
  • 函数体内部有且只能有一条return语句。函数体内可以包含其他语句,但必须这些语句在运行时不执行任何操作,如空语句、类型别名已经using声明。

constrxpr函数不一定返回常量表达式(如果实参不是字面值)

 

6.5.3 调试帮助

assert预处理宏

#include <cassert>
assert(expr);

表达式结果为真不做任何处理;表达式结果为假,assert输出信息并终止程序的执行。

assert应该仅用于验证那些确实不可能发生的事情。

NDEBUG预处理变量

assert的行为依赖于NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认情况下没有定义NDEBUG,此时assert将执行运行时检查。

我们可以使用一个#define语句定义NDEBUG,从而关闭调试状态。也可以在编译时添加选项,-D NDEBUG。

_ _ func _ _ ,编译器定义的一个局部静态变量,用于在函数内部表示函数名。

_ _ FILE _ _,存放文件名的字符串字面值。

_ _ LINE _ _, 存放当前行号的整型字面值。

_ _ TIME _ _,存放文件编译时间的字符串字面值。

_ _ DATE _ _, 存放文件编译日期的字符串字面值。

6.6 函数匹配    

候选函数和可行函数

候选函数包括在调用点可见的所有同名函数。

可行函数为候选函数内参数列表匹配的函数。

寻找最佳匹配

该函数的每个实参的匹配都不劣于其他可行函数需要的匹配。

至少有一个实参的匹配优于其他可行函数提供的匹配。

有且只有一个函数满足上述条件,则匹配成功。

 

调用重载类型时应尽可能避免强制类型转换

6.6.1 实参类型转换等级

 

6.7 函数指针 

当我们把函数名作为一个值使用时,该函数自动地转换成指针,因此用函数名给函数指针赋值时&符号可选。

使用函数指针调用函数,无需提前解引用指针;当然,如果提前解引用了也并无错误。

函数指针形参

 

使用类型别名和decltype简化使用函数指针的代码:

 

因为decltype返回函数类型,所以在结果前加上*才能得到指针。

返回指向函数的指针 

一般使用类型别名。

使用auto或者decltype(需要显式地加上*以表明返回指针,而非函数本身)

 

第七章 类

 定义在类内部的函数是隐式的inline函数(如果是声明在类内,但是定义在类外的函数,如果想要,要显式inline)

类对象的this指针类型是: 类类型 *const。

常量对象,以及常量对象的引用或指针都只能调用常量成员函数。

编译器首先编译成员的全部声明,然后才轮到成员函数的定义(如果有的话)。

const成员函数

紧跟在参数列表后面的const表示this是一个指向常量的指针,即类型为:const 类类型 *const

7.1.3 定义类相关的非成员函数

如果非成员函数是类的接口的组成部分,则这些函数的声明应该与类在同一个头文件内。

合成的默认构造函数

按照如下规则初始化类的数据成员:

  • 如果存在类内的初始值,用它来初始化成员。
  • 否则,默认初始化该成员。

=default的含义

在C++11标准,如果我们需要默认的行为,可以通过在参数列表后面写上=default来要求编译器生成构造函数。其中,=default可以和声明一起出现在类的内部,则默认构造函数是内联的;也可以作为定义出现在类的外部,此时该成员函数默认情况下不是内联的。

构造函数初始值列表

构造函数不应该轻易覆盖类内的初始值,除非新赋的值与原值不同。如果你不能使用类内初始值,则所有构造函数都应该显示地初始化每个内置类型的成员或没有默认构造的成员。

7.2 访问控制与封装

出于统一编程风格的考虑,当我们希望定义的类的所有成员是public的时候,使用struct;反之,使用class。

7.2.1 友元

友元不是类的成员也不受它所在区域访问控制级别的约束。

友元的声明仅仅指定了访问的权限,并非一个通常意义上的函数声明。(没有起到声明的作用,只是指定了友元函数可以访问类内的私有成员)

友元为类的非成员接口函数提供了访问其私有成员的能力。

7.3 类的其他特性

7.3.1 类成员再探

定义一个类型成员

在类内自定义某种类型在类中的别名,该别名存在访问限制。

用来定义类型的成员必须先定义后使用。

可变数据成员

声明时添加mutable关键字

7.3.2 返回*this的成员函数

从const成员函数返回*this

因为是const成员函数,所以对象指针是const *。哪怕调用的是非常量的对象,返回得到的对象也是常量,不符合链式设计。

基于const的重载

 

 

实现常量对象调用const成员函数,非常量对象调用普通成员函数。

类成员函数有一个隐式传参,指向对象的指针。所以可以根据底层const重载。

7.3.3 类类型

在类声明之后定义之前是一个不完全类型。可以用于函数的声明(但不能定义)。

我们可以定义指向不完全类型的指针或引用,但不能创建不完全类型的对象。

7.3.4 友元再探

友元函数能定义在类的内部,这样的函数是隐式内联的。

函数重载与友元

如果想把一组重载函数声明成友元,需要对这组函数的每一个分别声明。

友元声明和作用域

友元声明的作用是影响访问权限,它本身并非普通意义上的声明。因此,调用友元,还需要另外声明。

7.4 类的作用域

函数的返回类型通常出现在函数名之前。因此,当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外。如果此时的返回类型定义于类内部,需要加上类作用域。

类型名要特殊处理

一般来说,内层作用域可以重新定义外层作用域中的名字,即使该名字已经在内层作用域中使用过。然而在类中,如果成员使用了外层作用域中的某个代表类型的名字,则类不能在之后重新定义该名字。

类中类型名的定义要放在使用之前。

7.5 构造函数再探

如果没有在构造函数的初始值列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。(构造函数体内执行的是赋值操作)

构造函数的初始值有时必不可少

如果成员是const或者引用,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。

成员初始化的顺序

构造函数初始值列表只声明用于初始化成员的值,而不限定初始化的具体执行顺序。

成员的初始化顺序与他们在类定义中出现顺序一致。

7.5.2 委托构造函数

C++11标准。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,它的成员初始值列表只有一个唯一的入口,就是类名本身。

 

 

接收istream&的构造函数也是委托构造函数,它委托给了默认构造函数,默认构造函数又接着委托给三参数构造函数。当这些委托的构造函数执行完后,接着执行istream&构造函数体的内容。

被委托的构造函数的初始值列表和函数体被依次执行。

7.5.3 默认构造函数的作用

 

 

对于编译器合成的默认构造函数来说,类类型的成员执行各自所属类的默认构造函数,内置类型和复合类型的成员只对定义在全局作用域中的对象执行初始化。

7.5.4 隐式的类类型转换

如果构造函数只接收一个实参,则它实际上定义了转换为此类类型的隐式转换机制,定义了一条从构造函数的参数类型向类类型隐式转换的规则。也称转换构造函数。

只允许一步类类型转换

注意:从字符串字面值转换成字符串类型也是一步类型转换。因为字符串字面值是C风格字符串。

抑制构造函数定义的隐式转换

使用explicit关键字声明,explicit只允许出现在类内的构造函数声明处。

管编译器不会将explicit的构造函数用于隐式转换过程,但是我们可以使用这样的构造函数显式地强制进行转换。

7.5.5 聚合类(struct)

聚合类虚满足以下条件:

  • 所有成员都是public
  • 没有定义任何构造函数
  • 没有类内初始值
  • 没有基类,也没有virtual函数

7.5.6 字面值常量类

数据成员都是字面值类型的聚合类是字面值常量类,某些类虽然不是聚合类,但是满足如下要求也是字面值常量类。

 

构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。

构造函数不能有返回值,constexpr拥有的唯一可执行语句就是返回值,则constexpr构造函数的函数体一般来说是空的。

7.6 类的静态成员函数

静态成员函数不能声明成const。

声明静态成员的关键字static只出现在类内部的声明语句。

要确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一个文件中。

静态成员可以是public,也可以是private。

因为类的声明不会进行内存空间的分配,所以类的static成员一般只能在类外定义。

为了防止重复定义,一般在.c而不在.h定义static成员。

静态成员的类内初始化

通常情况下,类的静态成员不应该在类的内部初始化。然而,我们可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr。初始值必须是常量表达式。

即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义以下该成员。同时,初始值在类的定义内提供了,类外定义就不需要带初始值。

静态成员能用于某些场景

静态数据成员的类型可以是不完全类型。

静态数据成员的类型可以就是它所属的类类型,而非静态数据成员则受到限制,只能声明成它所属类的指针或引用。

静态数据成员可以作为默认实参。

 

这篇关于C++ Primer笔记--Part2 C++基础的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!