C++ Primer - 第一部分 C++基础

C++ Primer 阅读笔记 - 第一部分 C++基础

开始学习

大一学过C语言,当时学的不是很好,但是后面接触到算法竞赛的时候就慢慢补上来了,而且增加了一些C++特性以及STL标准模板库,也靠着半吊子C++拿了一些小奖,但是确实没有系统的学过C++。总之听说C++比较难,这次准备半系统性的学习一下。之前会的东西就做做题简单过一下,不会的重点看,尤其是指针和面向对象方面。希望以后能更加得心应手地使用C++,也为后面求职打打基础。

第1章 开始

注释

std::cout << "/*";
std::cout << "*/";
std::cout << /*  "*/" *.;
std::cout << /* "*/" /* "/*" */;

前两行没问题,注释只有一边,编译运行顺利通过

第三行注释全,但是字符串不全,缺少右边的",编译运行不能通过

第四行两边分别有两组注释,且中间的字符串是全的,因此编译运行顺利通过

读取数量不定的输入

int sum = 0,value = 0;
while(std::cin >> value){
    sum += value;
}
std::cout << sum << std::endl;

读取数量不定的整数,将其加和。

std::cin属于一种 istream对象,将其作为条件时是检测流的状态,遇到文件结束符或者无效输入时会变为无效,从而退出循环。

在Ubuntu中输入 Ctrl+D来输入一个文件结束符。

类简介

定义好的头文件:

#ifndef SALESITEM_H
// we're here only if SALESITEM_H has not yet been defined 
#define SALESITEM_H

// Definition of Sales_item class and related functions goes here
#include <iostream>
#include <string>

class Sales_item {
// these declarations are explained section 7.2.1, p. 270 
// and in chapter 14, pages 557, 558, 561
friend std::istream& operator>>(std::istream&, Sales_item&);
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
friend bool operator<(const Sales_item&, const Sales_item&);
friend bool 
operator==(const Sales_item&, const Sales_item&);
public:
    // constructors are explained in section 7.1.4, pages 262 - 265
    // default constructor needed to initialize members of built-in type
    Sales_item() = default;
    Sales_item(const std::string &book): bookNo(book) { }
    Sales_item(std::istream &is) { is >> *this; }
public:
    // operations on Sales_item objects
    // member binary operator: left-hand operand bound to implicit this pointer
    Sales_item& operator+=(const Sales_item&);
  
    // operations on Sales_item objects
    std::string isbn() const { return bookNo; }
    double avg_price() const;
// private members as before
private:
    std::string bookNo;      // implicitly initialized to the empty string
    unsigned units_sold = 0; // explicitly initialized
    double revenue = 0.0;
};

// used in chapter 10
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs) 
{ return lhs.isbn() == rhs.isbn(); }

// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);

inline bool 
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
    // must be made a friend of Sales_item
    return lhs.units_sold == rhs.units_sold &&
           lhs.revenue == rhs.revenue &&
           lhs.isbn() == rhs.isbn();
}

inline bool 
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
    return !(lhs == rhs); // != defined in terms of operator==
}

// assumes that both objects refer to the same ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs) 
{
    units_sold += rhs.units_sold; 
    revenue += rhs.revenue; 
    return *this;
}

// assumes that both objects refer to the same ISBN
Sales_item 
operator+(const Sales_item& lhs, const Sales_item& rhs) 
{
    Sales_item ret(lhs);  // copy (|lhs|) into a local object that we'll return
    ret += rhs;           // add in the contents of (|rhs|) 
    return ret;           // return (|ret|) by value
}

std::istream& 
operator>>(std::istream& in, Sales_item& s)
{
    double price;
    in >> s.bookNo >> s.units_sold >> price;
    // check that the inputs succeeded
    if (in)
        s.revenue = s.units_sold * price;
    else 
        s = Sales_item();  // input failed: reset object to default state
    return in;
}

std::ostream& 
operator<<(std::ostream& out, const Sales_item& s)
{
    out << s.isbn() << " " << s.units_sold << " "
        << s.revenue << " " << s.avg_price();
    return out;
}

double Sales_item::avg_price() const
{
    if (units_sold) 
        return revenue/units_sold; 
    else 
        return 0;
}
#endif

暂时不用怎么管,先试着使用:

  1. 读取单价和数量,输出总价格
Sales_item book; // 创建一个对象
std::cin >> book;
std::cout << book << std::endl;
return 0;
0-201-70353-x 4 24.99
> 0-201-70353-x 4 99.96 24.99
  1. 对象相加,输出总价格和平均价格
Sales_item book1,book2; // 创建一个对象
std::cin >> book1 >> book2;
std::cout << book1+book2 << std::endl;
return 0;
0-201-70353-x 3 20.00
0-201-70353-x 2 25.00
> 0-201-70353-x 5 110 22
  1. 增加成员函数,加和之前先判断两书的序列号是否相等
Sales_item book1,book2; // 创建一个对象
std::cin >> book1 >> book2;
if(book1.isbn() == book2.isbn()){
    std::cout << book1+book2 << std::endl;
}
else{
    std::cerr << "Error!" << std::endl;
}
return 0;
0-201-70353-x 3 20.00
0-201-70343-x 2 25.00
> Error!
  1. 读取销售记录,生成每本书的销售报告
#include <bits/stdc++.h>
#include "Sales_item.h"
int main(void){
    Sales_item total;
    if(std::cin >> total){ // 读取第一条数据,确保有数据可以处理
        Sales_item trans;
        while(std::cin >> trans){
            if(total.isbn() == trans.isbn()){
                total += trans;
            }
            else{
                std::cout << total << std::endl;
                total = trans;
            }
        }
        std::cout << total << std::endl; // 打印最后一本书
    }
    else{
        std::cerr << "No data!" << std::endl;
    }
    return 0;
}
0-201-70353-X 4 24.99
0-201-82470-1 4 45.39
0-201-88954-4 2 15.00 
0-201-88954-4 5 12.00 
0-201-88954-4 7 12.00 
0-201-88954-4 2 12.00 
0-399-82477-1 2 45.39
0-399-82477-1 3 45.39
0-201-78345-X 3 20.00
0-201-78345-X 2 25.00
> 
0-201-70353-X 4 99.96 24.99
0-201-82470-1 4 181.56 45.39
0-201-88954-4 16 198 12.375
0-399-82477-1 5 226.95 45.39
0-201-78345-X 5 110 22

这个程序的局限性在于,必须是连号的输入,不连号的输入就失效了。

当然这个时候学到的还不多,后面会将这个程序继续完善。

第2章 变量和基本类型

整型可以分为带符号类型和无符号类型(在前面添加 unsigned

选择类型的原则:

  1. 明确知道不可能为负值时,选用无符号类型
  2. 整数运算使用int,超过范围了使用long long
  3. 浮点数运算使用double
  4. 不要在算术表达式中使用char或者bool
  5. 不要混用无符号类型和带符号类型,因为带符号类型会自动转换为无符号类型,运算过程中出现负值即错误

初始化

创建变量时赋予其一个初始值(赋值指的是将对象的当前值用一个新值来替代,含义不同)

初始化的4种方式:

  1. int a = 0;
  2. int a = {0};
  3. int a{0}; // 列表初始化
  4. int a(0);

变量声明:“一个文件如果想使用别处定义的名字,必须包含对那个名字的声明”

与定义的区别在于不赋初值

extern int i;

作用域

  1. 作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问该名字
  2. 允许在内层作用域中重新定义外层作用域已有的名字

如:

int a = 0;
int main(void){
    std::cout << a << std::endl;
    int a = 1;
    std::cout << a << std::endl;
    std::cout << ::a << std::endl; // 显式指定访问全局变量
    return 0;
}
0
1
0

引用

相当于为对象起一个另外的名字,通过 &符号来定义

int ival = 1024;
int &refVal = ival;

引用必须初始化,因为引用需要和它的初始化对象一起绑定在一起,不能重新绑定到其他对象。

定义引用之后,对其进行的所有操作都是在它的绑定对象上进行的

refVal = 12;
std::cout << refVal << std::endl;
12

引用本身不是一个对象,不能定义引用的引用

如下面的方式,实际上是绑定到了该引用对应的绑定对象上:

int &refVal2 = refVal;
std::cout << refVal2 << std::endl;
12

引用的类型要与绑定的对象严格匹配

引用不能绑定到字面值上

指针

指针也实现了对其他对象的间接访问,但是指针本身也是一个对象,通过 *符号来定义

  1. 指针存放某个对象的地址,如果获取这个地址,需要使用取地址符 &
int ival = 42;
int *p = &ival;

指针的类型也要与它所指向的对象严格匹配

  1. 如果指针指向了一个对象,可以使用解引用符 *来访问这个对象
int ival = 42;
int *p = &ival;
std::cout << *p << std::endl;
42

符号的多重含义:

int i = 42;
int &r = i; // &随类型名出现,是声明的一部分,r是一个引用
int *p; // *随类型名出现,是声明的一部分,p是一个指针
p = &i; // &出现在表达式中,是一个取地址符
*p = i; // *出现在表达式中,是一个解引用符
int &r2 = *p; // r2是一个引用,*是一个解引用符
std::cout << i << std::endl << r << std::endl << *p << std::endl << r2 << std::endl;
42
42
42
42

空指针:int *p1 = nullptr

建议:初始化所有的指针

指针与引用不同,是可以赋值的。赋值的时候永远改变的是等号左侧的对象。

void*指针,可以用于存放任意类型对象的地址

double obj = 3.14;
double *pd = &obj;
void *pv = &obj;
pv = pd;
std::cout << *pv << std::endl;
error: ‘void*’ is not a pointer-to-object type

void*指针只能与其他指针作比较,作为函数的输入和输出,或者赋值给另外一个 void*指针。

甚至连访问对象都不可以

指向指针的指针

int ival = 1024;
int *pi = &ival;
int **ppi = π
std::cout << ival << std::endl;
std::cout << *pi << std::endl;
std::cout << **ppi << std::endl;
1024
1024
1024

指向指针的引用

int i = 42;
int *p;
int *&r = p;
r = &i; // p = &i;
std::cout << i << std::endl;
std::cout << *r << std::endl;
std::cout << *p << std::endl;
42
42
42

阅读定义要从右往左,离变量名最近的符号对变量类型有最直接的影响

最近的是 &,因此 r是一个引用

然后是 *,说明 r引用的是一个指针

const

const对象一旦创建,值不可以再改变,因此在创建的时候必须初始化

const int a = 45;

只能在 const类型的对象上执行不改变其内容的操作

可以添加extern关键字,使const变量在文件间共享

extern const int bufSize = fcn(); // file.cpp定义并初始化了这个常量,可以被其他文件访问
extern const int bufSize; // file.h 和上面的变量是同一个,只是一个声明,说明定义会在其他地方出现

const的引用是对常量的引用,不能改变引用的值,引用的时候也要添加 const限定符

const int ci = 1024;
const int &r1 = ci;

初始化常量引用时可以使用任意的表达式,只要表达式的结果能转化成引用的类型即可

int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
std::cout << r1 << std::endl;
std::cout << r2 << std::endl;
std::cout << r3 << std::endl;
i = 56;
std::cout << r1 << std::endl;
std::cout << r2 << std::endl;
std::cout << r3 << std::endl;
42
42
84
56
42
84

因此,对 const的引用可以并非一个 const的对象,不能通过这种引用改变被引用的对象的值,但是可以通过其他方式改变这个对象的值

指向常量的指针也不能用于改变其所指对象的值,且指向常量的指针所指的对象也不一定是一个常量

const double pi = 3.14;
const double *cptr = &pi
std::cout << *cptr << std::endl;
double dval = 3.14;
cptr = &dval;
std::cout << *cptr << std::endl;
3.14
3.14

const指针:将指针本身定义为常量,也就是指针所指的地址不变

int errNumb = 0;
int *const curErr = &errNumb; // curErr将一直指向errNumb,不能改变
const double pi = 3.14159;
const double *const pip = &pi // 一个指向常量对象的常量指针
*curErr = 56;
std::cout << errNumb << std::endl;
56

指针所指的地址不变,但是如果指向的不是常量,还是可以改变指向的值的

顶层 const可以表示任意的对象是一个常量,底层 const与复合类型有关,指的是下一层对象是常量。

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

将变量声明为 constexpr来由编译器验证是否为一个常量表达式:constexpr int limit = mf + 1;

constexpr中如果声明了一个指针,那么一定是常量指针,即顶层 const

处理变量类型

类型别名的两种定义方式:

typedef double wages;
using wages = double;

如果别名是一个复合类型,不能仅仅将其替换进行理解。

auto类型:将类型交给编译器自己去分析,一般会忽略掉顶层 const,如果需要保留要加 const auto进行推断

decltype类型指示符:通过表达式的类型推断出要定义的变量的类型

const int a = 0,b = 0;
decltype(a+b) x = 0;
std::cout << x << std::endl;
0

如果希望得到引用类型,可以添加两层括号,即 decltype((a+b))

自定义数据结构

struct Sales_data{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

读取单价和数量,输出总价格

Sales_item book; // 创建一个对象
std::cin >> book;
std::cout << book << std::endl;
return 0;
0-201-70353-x 4 24.99
> 0-201-70353-x 4 99.96 24.99

头文件:包含只能被定义一起的实体

通过头文件保护符来确保不允许重复包含:

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
#endif

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

using声明:使用命名空间中的成员

using std::cin;

头文件不应包含 using声明

标准库类型string

定义和初始化

string s1;
string s2(s1); // string s2 = s1;
string s3("value"); // string s3 = "value";
string s4(10,'c');
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;


value
cccccccccc

初始化分为直接初始化和拷贝初始化,有 =的为拷贝初始化,一般只用于单个初始值的情况下

string对象的操作

输入输出与对整数等的操作相同

使用getline读入一整行(可以带空格)

string line;
while(getline(cin,line)){
    cout << line << endl;
}
return 0;
> fds fdsfdsf dsf
fds fdsfdsf dsf
> dsfdsfdsfds fdsfds 
dsfdsfdsfds fdsfds

string.size()返回的是无符号整形数,不要去负数值混用

字面值不为字符串,不能将字面值相加,只能将字符串相加,如 "df"+"fdsfs"是不合法的

基于范围的 for语句:遍历给定序列中的每一个元素

string str("some string");
for (auto c : str){
    cout << c;
}
cout << endl;
some string

如果要改变字符,需要使用引用类型:

string str("some string");
for (auto &c : str){
    c = toupper(c);
}
cout << str << endl;
SOME STRING

标准库类型vector

vector属于一个类模板,模板不是类或者函数,但是可以看作编译器生成类或函数编写的一份说明,编译器根据模板创建类或函数的过程称为实例化

定义和初始化vector对象

vector<int> ivec;
vector<int> ivec2(ivec);
vector<int> ivec3 = ivec;
vector<string> articles{"a","an","the"};
vector<string> svec(10,"hi");
for(auto i : svec){
    cout << i << " ";
}
cout << endl;
hi hi hi hi hi hi hi hi hi hi

值初始化:只初始化 vector的大小,不赋值具体数值 vector<int> i(10)

其他vector操作

vector在设计上事先指定容量是不好的做法,比较适合运行时再添加具体的值

循环内部如果包含向 vector添加元素的语句,不能使用范围 for循环

不能用下标形式添加元素,也就是下标操作只能对确知已经存在的元素进行

迭代器

vector<int> vi = {1,2,4,5,7,8,9,5,6,4};
for(auto it1 = vi.begin();it1 != vi.end();it1++){
    *it1 *= 2;
}
for(auto it2 = vi.cbegin();it2 != vi.cend();it2++){
    cout << *it2 << " ";
}
cout << endl;
2 4 8 10 14 16 18 10 12 8

数组

定义与初始化

int a2[] = {0,1,2};
int a3[5] = {1,2,4}; // 多余的初始化成默认值
char a4[8] = "Daniel"; // 至少是7,要有一个空字符
for (auto i: a3){
    cout << i << " ";
}
cout << endl;
for (auto i: a4){
    cout << i << " ";
}
cout << endl;
1 2 4 0 0 
D a n i e l

数组不允许拷贝和赋值

复杂的数组声明:

int *ptrs[10]; // 含有10个整型指针的数组
int arr[10];
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; // arry是数组的引用,该数组含有10个指针

指针和数组

使用数组的时候编译器一般将其转化为指针

string nums[] = {"one","two","three"};
string *p = &nums[0]; // p指向nums的第1个元素
string *p2 = nums // 等价于上面的语句

使用 auto推断时会返回一个指针,但是只用 decltype推断的时候会返回数组

利用指针对数组可以起到迭代器的效果

int ia[] = {1,2,3,4,5,6,7,8,9};
int *beg = begin(ia); // 指向ia的第一个元素
int *last = end(ia); // 指向ia的最后一个元素的下一个位置
for(auto it = beg;it != last;it++){
    cout << *it << " ";
}
cout << endl;

C风格字符串

string转化为C风格字符串

string s("Hello World!");
const char *str = s.c_str();
cout << *str << endl;

使用数组初始化vector

int int_arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));
for(auto it : ivec){
    cout << it << " ";
}
cout << endl;
0 1 2 3 4 5

指针和多维数组

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*p)[4] = ia; // p指向含有4个整数的数组
p = &ia[2]; // p 指向ia的尾元素
for(auto p = ia;p != ia+3;++p){
    for(auto q = *p;q != *p+4;q++){
        cout << *q << " ";
    }
}
cout << endl;
for(auto p = begin(ia);p != end(ia);++p){
    for(auto q = begin(*p);q != end(*p);q++){
        cout << *q << " ";
    }
}
cout << endl;

第4章 表达式

通俗的讲,左值就是能够出现在赋值符号左面的东西,而右值就是那些可以出现在赋值符号右面的东西.

左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象

右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字。

当一个对象被用作右值的时候,使用的是对象的值(内容);当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置)

  1. 算术运算符的运算结果和求值对象都是右值

m%n的符号与 m相同

  1. 逻辑和关系运算符的运算结果和求值对象都是右值
  2. 赋值运算符的左侧运算对象必须是一个可修改的左值,结果是他的左侧运算对象,并且是一个左值
  3. 递增和递减运算符必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回
  4. 箭头运算符作用于一个指针类型的运算对象,结果是一个左值
  5. 点运算符的结果与成员所属的对象相同
  6. 条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值,否则运算的结果是右值

强制类型转换:

int i = 52;
int j = 9;
double slope = static_cast<double>(j) / i;
cout << slope << endl;
0.173077

第5章 语句

switch语句:

int a;
while(cin >> a){
    switch(a){
        case 0: cout << '1' << endl;break;
        case 1: cout << '2' << endl;break;
        case 2: cout << '3' << endl;break;
        case 3: cout << '4' << endl;break;
        case 4: cout << '5' << endl;break;
        case 5: cout << '6' << endl;break;
        case 6: cout << '7' << endl;break;
        case 7: cout << '8' << endl;break;
        case 8: cout << '9' << endl;break;
        case 9: cout << '0' << endl;break;
        default: cout << 'N' << endl;break;
    }
}
> 0
1
> 9
0
> 45
N

try语句块和异常处理:

throw语句抛出异常:

int a = 1;
throw runtime_error("fdsdfds");
terminate called after throwing an instance of 'std::runtime_error'
  what():  fdsdfds
Aborted

catch语句捕捉异常:

double m, n;
cin >> m >> n;
try {
    if (n == 0)
        throw - 1;  //抛出整型异常
    else if (m == 0)
        throw - 1.0;  //拋出 double 型异常
    else
        cout << m / n << endl;
}
catch (double d) {
    cout << "catch (double)" << d << endl;
}
catch (...) {
    cout << "catch (...)" << endl;
}
> 0 6
catch (double)-1
> 6 0
catch (...)

第6章 函数

局部静态对象:程序第一次经过时被初始化,直到程序终止时才被销毁。

int count_calls(){
    static int ctr = -1;
    return ++ctr;
}

int main(void){
    for(int i = 0;i != 10; ++i){
        cout << count_calls() << " ";
    }
    cout << endl;
    return 0;
}
0 1 2 3 4 5 6 7 8 9

函数声明(函数原型):在使用函数之前对函数的名字进行声明

函数声明可以忽略形参的名字,也可以加上形参的名字。

函数声明最好写在头文件中

分离式编译:编译和链接多个源文件

参数传递

指针形参:

void reset(int *ip){
    *ip = 0;
}

int main(void){
    int i = 42;
    reset(&i);
    cout << i << endl;
    return 0;
}
0

传引用参数:

void reset(int &i){
    i = 0;
}

int main(void){
    int i = 42;
    reset(i);
    cout << i << endl;
    return 0;
}
0

尽量使用引用形式从而避免拷贝

还可以通过引用形式返回一些额外信息。因为函数只能返回一个返回值,但是如果某个值是引用的形式传到函数中的,也会保留下修改后的值。

不修改的变量尽量使用常量引用

数组形参:

void Print(const int i[]){
    cout << i[0] << endl;
}

void Print(const int *i){
    cout << i[0] << endl;
}

void Print(const int i[10]){
    cout << i[0] << endl;
}

int main(void){
    int i = 5;
    int j[2] = {6,7};
    Print(&i);
    Print(j);
}
5
6

数组不能直接进行传递,直接作为指针的形式传递,因此丢掉了数组大小的信息

可以使用指针的形式进行提示,也可以传入一个表示数组大小的参数。

void print(const int *beg,const int *end){
    while(beg != end){
        cout << *beg++ << " ";
    }
}

void print(const int i[] ,size_t size){
    for(size_t a=0;a<size;a++){
        cout << i[a] << " ";
    }
}

int main(void){
    int i[10] = {6,7,5,4,7,8,9,6,5,4};
    print(begin(i),end(i));
    cout << endl;
    return 0;
}
6 7 5 4 7 8 9 6 5 4

数组引用形参:(缺点是只能作用于大小固定的数组)

void print(int (&arr)[10]){
    for(auto elem : arr){
        cout << elem << " ";
    }
}

含有可变形参的函数:

void error_msg(initializer_list<string> il){
    for(auto beg = il.begin();beg != il.end();++beg){
        cout << *beg << " ";
    }
    cout << endl;
}

int main(void){
    error_msg({"a","b"});
    error_msg({"a","b","c"});
}
a b 
a b c

函数的返回值

函数返回时不要返回局部对象的引用或指针

调用一个返回引用的函数会得到左值

char &get_val(string &str,string::size_type ix){
    return str[ix];
}

int main(void){
    string s("a value");
    cout << s << endl;
    get_val(s,0) = 'A';
    cout << s << endl;
    return 0;
}
a value
A value

返回值也可以是一个花括号包围起来的列表

函数重载

定义相同名称的函数,但是形参列表不同,可能是数量上的不同,也可能是类型上的不同。使得函数调用的时候根据不同的形参列表自动判断指定哪一个函数。

顶层 const不影响传入的参数

在不同的作用域中无法重载函数,会覆盖掉

特殊用途语言特性

默认实参:在函数的声明中给一个默认值,调用时可以覆盖掉,也可以不写以使用默认值。

内联函数:将函数在调用点展开,但是编译器不一定支持

constexpr函数:能用于常量表达式的函数,函数的返回值和所有形参的类型都要是字面值类型,函数体中有且只有一条 return语句。

assert表达式:用于调试的时候对程序进行检查 assert(s == "dfdsf");如果不满足条件程序会中断退出。

函数指针

完全不明白。。。没有示例程序看不懂

第7章 类

定义抽象数据类型

成员函数是类定义的一部分,通过特定的对象来调用。非成员函数就是普通的函数。

成员函数的声明必须在类的内部,定义可以在类的内部或者外部。非成员函数的声明和定义都在类的外部。

构造函数:控制对象的初始化过程

访问控制与封装:

定义在public说明符后的成员在整个程序内可被访问,public成员定义类的接口。

定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了类的实现细节。

使用class和struct定义类的区别在于默认的访问权限不同,struct默认访问权限都是public的

友元:令其他类或成员成为访问它的非公有成员。但是友元只算一个权限控制,在类外一样要进行声明。

上述代码:

#include <bits/stdc++.h>
using std::string;
using std::vector;
using std::cin;
using std::cout;
using std::endl;
using std::begin;
using std::end;
using std::runtime_error;
using std::initializer_list;
using std::istream;
using std::ostream;

class Sales_data{
friend Sales_data add(const Sales_data&,const Sales_data&);
friend std::ostream &print(std::ostream&,const Sales_data&);
friend std::istream &read(std::istream&,Sales_data&);
public:
    // 构造函数
    Sales_data() = default; // 默认构造函数
    // 构造函数初始值列表
    Sales_data(const std::string &s):bookNo(s){ }
    Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){ }
    Sales_data(std::istream &);
    // 常量成员函数
    std::string isbn() const {
        return bookNo;
    }
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
Sales_data add(const Sales_data&,const Sales_data&);
std::ostream &print(std::ostream&,const Sales_data&);
std::istream &read(std::istream&,Sales_data&);


// 在类的外部定义成员函数
double Sales_data::avg_price() const {
    if(units_sold){
        return revenue / units_sold;
    }
    else{
        return 0;
    }
}

// 定义一个返回this对象的函数
Sales_data& Sales_data::combine(const Sales_data &rhs){
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

// 类相关的非成员函数
istream &read(istream &is, Sales_data &item){
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}

ostream &print(ostream &os,const Sales_data &item){
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

Sales_data add(const Sales_data &lhs,const Sales_data &rhs){
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

// 在类的外部定义构造函数
Sales_data::Sales_data(std::istream &is){
    read(is,*this);
}

int main(void){
    return 0;
}

类的其他特性

  1. 定义一个类型成员:一般写在最开头的位置
  2. 令成员作为内联函数:可以将 inline写在类内或者类外,一般写在类外
  3. 重载成员函数
  4. 可变数据成员:在 const里面也可以变化
  5. 类数据成员的初始值:一个类里面由另外一个类提供初始值
class Screen{
    public:
        typedef std::string::size_type pos; // 定义类型的成员
        Screen() = default;
        Screen(pos ht,pos wd,char c): height(ht), width(wd),contents(ht*wd,c){ }
        char get() const {
            return contents[cursor]; // 隐式内联函数
        };
        inline char get(pos ht,pos wd) const; // 显式内联函数
        Screen &move(pos r, pos c); // 后面设置为内联函数
        size_t some_member() const;
        Screen &set(char);
        Screen &set(pos,pos,char);
        Screen &display(std::ostream &os){
            do_display(os);
            return *this;
        }
        const Screen &display(std::ostream &os) const {
            do_display(os);
            return *this;
        }
    private:
        pos cursor = 0;
        pos height = 0,width = 0;
        std::string contents;
        mutable size_t access_ctr = 0;
        void do_display(std::ostream &os) const {
            os << contents;
        }
};

inline Screen &Screen::move(pos r,pos c){
    pos row = r * width;
    cursor = row + c;
    return *this;
}

char Screen::get(pos r,pos c) const {
    pos row = r * width;
    return contents[row + c];
}

size_t Screen::some_member() const{
    ++access_ctr;
    return access_ctr;
}

inline Screen &Screen::set(char c){
    contents[cursor] = c;
    return *this;
}

inline Screen &Screen::set(pos r,pos col,char ch){
    contents[r*width+col] = ch;
    return *this;
}

// 类数据成员的初始值
class Window_mgr{
    private:
        std::vector<Screen> screens{Screen(24,80,' ')};
};

int main(void){
    Screen myscreen;
    char ch = myscreen.get();
    cout << myscreen.some_member() << endl;
    ch = myscreen.get(0,0);
    cout << myscreen.some_member() << endl;
    myscreen.move(4,0);
    myscreen.set('#');
    cout << myscreen.get() << endl;
    Screen myScreen(5,3,'!');
    const Screen blank(5,3,'?');
    myScreen.set(2,1,'#').display(cout);
    cout << endl;
    blank.display(cout);
    cout << endl;
    return 0;
}
1
2
#
!!!!!!!#!!!!!!!
???????????????

类之间的友元关系:不存在传递性

class Screen{
    friend class Window_mgr;

// 类数据成员的初始值
class Window_mgr{
    public:
        using ScreenIndex = std::vector<Screen>::size_type;
        void clear(ScreenIndex);
    private:
        std::vector<Screen> screens{Screen(24,80,' ')};
};

void Window_mgr::clear(ScreenIndex i){
    Screen &s = screens[i];
    s.contents = string(s.height * s.width, ' ');
}

类的作用域

class Screen{
    friend class Window_mgr;
    public:
        typedef std::string::size_type pos; // 定义类型的成员
        Screen() = default;
        Screen(pos ht,pos wd,char c): height(ht), width(wd),contents(ht*wd,c){ }
        char get() const {
            return contents[cursor]; // 隐式内联函数
        };
        inline char get(pos ht,pos wd) const; // 显式内联函数
        Screen &move(pos r, pos c); // 后面设置为内联函数
        size_t some_member() const;
        Screen &set(char);
        Screen &set(pos,pos,char);
        Screen &display(std::ostream &os){
            do_display(os);
            return *this;
        }
        const Screen &display(std::ostream &os) const {
            do_display(os);
            return *this;
        }
    private:
        pos cursor = 0;
        pos height = 0,width = 0;
        std::string contents;
        mutable size_t access_ctr = 0;
        void do_display(std::ostream &os) const {
            os << contents;
        }
};

inline Screen &Screen::move(pos r,pos c){
    pos row = r * width;
    cursor = row + c;
    return *this;
}

char Screen::get(pos r,pos c) const {
    pos row = r * width;
    return contents[row + c];
}

size_t Screen::some_member() const{
    ++access_ctr;
    return access_ctr;
}

inline Screen &Screen::set(char c){
    contents[cursor] = c;
    return *this;
}

inline Screen &Screen::set(pos r,pos col,char ch){
    contents[r*width+col] = ch;
    return *this;
}

// 类数据成员的初始值
class Window_mgr{
    public:
        using ScreenIndex = std::vector<Screen>::size_type;
        void clear(ScreenIndex);
        ScreenIndex addScreen(const Screen&);
    private:
        std::vector<Screen> screens{Screen(24,80,' ')};
};

void Window_mgr::clear(ScreenIndex i){
    Screen &s = screens[i];
    s.contents = string(s.height * s.width, ' ');
}

Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s){
    screens.push_back(s);
    return screens.size() - 1;
}

int main(void){
    Screen myscreen;
    char ch = myscreen.get();
    cout << myscreen.some_member() << endl;
    ch = myscreen.get(0,0);
    cout << myscreen.some_member() << endl;
    myscreen.move(4,0);
    myscreen.set('#');
    cout << myscreen.get() << endl;
    Screen myScreen(5,3,'!');
    const Screen blank(5,3,'?');
    myScreen.set(2,1,'#').display(cout);
    cout << endl;
    blank.display(cout);
    cout << endl;
    return 0;
}

构造函数进阶

构造函数初始值列表:定义变量的时候习惯对其立即进行初始化,有时初始化的值是必不可少的,且要注意成员初始化的顺序。

委托构造函数:使用它所属类的其他构造函数执行它自己的初始化过程

默认构造函数

类类型转换

聚合类:就是比较简单的结构体

字面值常量类

类的静态成员

类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据


C++ Primer - 第一部分 C++基础
https://zhangzhao219.github.io/2022/08/09/c-plus-basic/
作者
Zhang Zhao
发布于
2022年8月9日
许可协议