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
暂时不用怎么管,先试着使用:
- 读取单价和数量,输出总价格
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
- 对象相加,输出总价格和平均价格
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
- 增加成员函数,加和之前先判断两书的序列号是否相等
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!
- 读取销售记录,生成每本书的销售报告
#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
)
选择类型的原则:
- 明确知道不可能为负值时,选用无符号类型
- 整数运算使用int,超过范围了使用long long
- 浮点数运算使用double
- 不要在算术表达式中使用char或者bool
- 不要混用无符号类型和带符号类型,因为带符号类型会自动转换为无符号类型,运算过程中出现负值即错误
初始化
创建变量时赋予其一个初始值(赋值指的是将对象的当前值用一个新值来替代,含义不同)
初始化的4种方式:
int a = 0;
int a = {0};
int a{0};
// 列表初始化int a(0);
变量声明:“一个文件如果想使用别处定义的名字,必须包含对那个名字的声明”
与定义的区别在于不赋初值
extern int i;
作用域
- 作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问该名字
- 允许在内层作用域中重新定义外层作用域已有的名字
如:
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
引用的类型要与绑定的对象严格匹配
引用不能绑定到字面值上
指针
指针也实现了对其他对象的间接访问,但是指针本身也是一个对象,通过 *
符号来定义
- 指针存放某个对象的地址,如果获取这个地址,需要使用取地址符
&
int ival = 42;
int *p = &ival;
指针的类型也要与它所指向的对象严格匹配
- 如果指针指向了一个对象,可以使用解引用符
*
来访问这个对象
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章 表达式
通俗的讲,左值就是能够出现在赋值符号左面的东西,而右值就是那些可以出现在赋值符号右面的东西.
左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象
右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字。
当一个对象被用作右值的时候,使用的是对象的值(内容);当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置)
- 算术运算符的运算结果和求值对象都是右值
m%n
的符号与 m
相同
- 逻辑和关系运算符的运算结果和求值对象都是右值
- 赋值运算符的左侧运算对象必须是一个可修改的左值,结果是他的左侧运算对象,并且是一个左值
- 递增和递减运算符必须作用于左值运算对象,前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回
- 箭头运算符作用于一个指针类型的运算对象,结果是一个左值
- 点运算符的结果与成员所属的对象相同
- 条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值,否则运算的结果是右值
强制类型转换:
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;
}
类的其他特性
- 定义一个类型成员:一般写在最开头的位置
- 令成员作为内联函数:可以将
inline
写在类内或者类外,一般写在类外 - 重载成员函数
- 可变数据成员:在
const
里面也可以变化 - 类数据成员的初始值:一个类里面由另外一个类提供初始值
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;
}
构造函数进阶
构造函数初始值列表:定义变量的时候习惯对其立即进行初始化,有时初始化的值是必不可少的,且要注意成员初始化的顺序。
委托构造函数:使用它所属类的其他构造函数执行它自己的初始化过程
默认构造函数
类类型转换
聚合类:就是比较简单的结构体
字面值常量类
类的静态成员
类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据