我的C++ primer笔记(第I部分)

文章目录


前言

本文是完全写给自己看的《C++ primer》笔记,记录系统学习C++过程中学到的知识,以及不了解的点,由于需要尽快阅读,加上之前用过很长时间的C+STL,只是没有系统的学习过C++,很多地方觉得自己会了就看了一遍没有仔细的写一下,也是因为感到时间不太够了,稍后有时间慢慢弥补上这些漏洞,有两三节忘记保存了emmm,过几天写上。第一部分的内容差不多就这样了,如果中间有什么不理解或者错误的问题可以私信联系我,或者联系qq:596986881。之后的内容也会持续更新。

第一章 开始

标准输入输出对象
标准库提供了四个IO对象:

  • cin是一个istream类型的对象,也被称为标准输入
  • cout是一个ostream类型的对象,也被称为标准输出
  • cerr,是一个ostream对象,我们通常用cerr来输出警告和错误消息,因此也被称为标准错误
  • clog,是一个ostream对象,用来输出程序运行时的一般性信息

endl
endl的效果是结束当前行,并将与设备关联的缓冲区中的内容刷到设备中。缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。

引入头文件规则
标准库使用<>,引入自己的头文件使用""

第I部分 C++基础

第二章 变量和基本类型

2.1基本内置类型

2.1.1算数类型

基本算数类型所占内存
long double常常被用于有特殊浮点需求的硬件,它的具体实现不同,精度也各不相同

2.1.2类型转换

类型转换超过范围时,其结果是未定义的,一定要注意!!!

含有无符号类型的表达式
当一个算数表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl;	//输出-84
std::cout << i + u << std::endl;	//如果int占32位,输出4294967264

切勿混用带符号类型和无符号类型,如果需要同时使用先强制类型转换

2.1.3字面值常量

一个形如42的值被称作字面值常量

20 /*十进制*/
024 /*八进制*/
0x14 /*十六进制*/

默认情况下十进制字面值是带符号数,八进制或者十六进制字面值既可能是带符号的有可能是无符号的

转义序列
在这里插入图片描述

指定字面值的类型
在这里插入图片描述
指针字面值:nullptr

2.2变量

2.2.1变量定义

列表初始化
以下四种都是合法的初始化方式

int a = 0;
int a = {0};
int a{0};
int a(0);
2.2.2声明变量和定义的关系

如果想要声明一个变量而非使用它,就在变量名前加上extern关键字,而不要显式地初始化变量

extern int i;	//声明i而非定义i
int j;	//定义j
2.2.3标识符

C++的标识符由字母、数字、下划线组成,其中必须以字母或下划线开头。标识符长度无限制,但对大小写敏感。

2.2.4名字的作用域

2.3复合类型

2.3.1引用
int a = 10;
int &r = a;

其中ra的引用,程序把引用和它的初始值绑定在一起,修改r时a也会改变
引用即别名
引用并非对象,相反的,它只是为一个已存在的对象所起的另外一个名字。

2.3.2指针
int *p1, *p2;	//p1、p2都是指向int类型的指针

获取对象的地址

int a = 42;
int *p = &a;

指针值
指针的值应属下面四种状态之一:

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一个位置。
  3. 空指针,意味着指针没有指向任何一个对象。
  4. 无效指针,也就是除上述情况下的其它值。

使用指针访问对象

int a = 42;
int *p = &a;
std::cout << *p << std::endl;

空指针
空指针不指向任何对象,在试图使用一个指针之前可以先检查指针是否为空。以下列出几个生成空指针的方法:

int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;	//首先需要#include<cstdlib>

两个合法的指针进行比较,两个指针存放的地址相同返回true,否则返回false。
下百年例子输出为YES

int *p1, *p2;
int t = 1;
p1 = &t, p2 = &t;

if(p1 == p2) puts("YES");
else puts("NO");

void* 指针
void*是一种特殊的指针类型,可以用于存放任意对象的地址。
在这里插入图片描述

2.3.3 理解符合类型的声明

定义多个变量

int *p1, *p2;	//p1、p2都是int类型的指针
int *p, q;		//p是int类型的指针,q是int

指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。

int i = 42;
int *p;
int *&r = p;	//r是p的引用

r = &i;	//让p指向i
*r = 0;	//让i的值变成0

2.4 const限定符

const对象必须初始化,初始值可以是任意复杂的表达式

const int a = 1;	//运行时初始化
const int b = get_size();	//编译时初始化

默认状态下const对象仅在文件内有效
想要const修饰的常量在多个文件中使用不管是声明还是定义都添加extern关键字即可

2.4.1const的引用

可以把引用绑定到const对象上,我们称之为对常量的引用。与普通引用不同的是,对常量的引用不能修改它绑定的对象。

2.4.2指针和const

想要存放常量对象的地址,必须使用指向常量的指针

const int a = 1;
const int *p = &a;

指针的指向可以更改,指向对象的值不可修改

const指针
把指针本身定义为常量,叫做指针常量

int a = 1, b = 1;
int *const p = &a;
a = 2;
std::cout << *p << '\n';

p的指向不能发生改变,a的值可以改变

指针不能改变指向的值,也不能改变指向

int main() {
    int a = 10, b = 10;
    
    //常量指针,即指向常量的指针:const修饰的是指针,指针指向可以修改,指针指向的值不可以修改
    const int * p1 = &a;
    p1 = &b; //正确
    //*p1 = 100 错误
    
    //指针常量,即指针是常量:const修饰的是常量,指针指向不可以修改,指针指向的值可以修改
    int * const p2 = &a;
    //p2 = &b 错误
    *p2 = 100; 	//正确
    
    //const即修饰指针又修饰常量,指向和指向的值均不可修改
    const int * const p = &a;
    
    return 0;
}
2.4.3顶层const

用名词顶层const表示指针本身是个常量,而用名词底层const表示指针所指的对象是一个常量

2.4.4constexpr和常量表达式

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

const int a = 1;	//a是常量表达式
const int b = a + 1;	//b是常量表达式
const int sz = get_size();	//sz不是常量表达式

声明为constexpr的变量一定是一个常量,并且必须用常量表达式初始化。

constexpr int a = 2;	//2是常量表达式
constexpr int b = a + 1;	//a + 1是常量表达式
constexpr int sz = size();	//只有当size是一个constexpr函数时这才是一条正确的语句

2.5类型处理

2.5.1类型别名

有两种方法可用于定义类型别名。传统方法是使用关键字typedef

typedef long long LL;	//LL是long long的别名

新标准规定了一种新的方法,使用别名声明来定义类型的别名:

using LL = long long;
2.5.2auto类型说明符

auto让编译器通过初始值来推算变量的类型。

2.5.3decltype类型指示符

decltype:选择并返回操作数的数据类型。

const int ci = 0, &cj = ci;
decltype(ci) x = 0;	//正确,x的类型是const int
decltype(cj) y = x;	//正确,y的类型是const int&,y绑定到x
decltype(cj) z;		//错误,z是一个引用,必须初始化

切记:decltype((var)),(注意是双层括号)的结果影院是引用,而单层括号结果只有当var本身就是一个引用时才是引用。

2.6自定义数据结构

2.6.1定义类型

使用classstruct

2.6.2使用类

自己写成员变量以及函数使用即可

2.6.3编写自己的头文件

将类写在指定头文件即可,使用时用#include"文件名"

头文件保护符
头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定位预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:#ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到**#endif**指令为止。

预处理变量无视C++中关于作用于的规则

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

3.1命名空间的using声明

using namespace std;	//std::都不需要加了
using std::cin;	//使用cin不需要加std::

3.2标准库类型string

3.2.1定义和初始化string对象
string s1;	//s1是空串
string s2 = s1;	//s2是s1的副本
string s3 = "abc";	//s3是该字面值的副本
string s4(10, 'c');	//s4的内容是cccccccccc

在这里插入图片描述
直接初始化和拷贝初始化
使用=是拷贝初始化,不使用则是直接初始化

3.2.2string对象上的操作

在这里插入图片描述
使用getline获取一行字符得到的string对象不包含换行符

string使用size()返回的是string::size_type类型,它是一个无符号类型,注意尽量不要混用int

使用string的+运算符时,必须保证两侧至少有一个是string对象

3.2.3处理string对象中的字符

直接使用下标访问就行

3.3标准库类型vector

3.3.1初始化vector对象

在这里插入图片描述

3.3.2向vector中添加元素
std::vector<int> s;
s.push_back(10);
3.3.3其它vector操作

在这里插入图片描述

3.4迭代器介绍

3.4.1使用迭代器
//由编译器决定a和b的类型
auto a = v.begin(), b = v.end();

end成员负责返回指向容器尾元素的下一个位置,被称为尾后迭代器,如果容器为空begin和end相同

迭代器运算符
在这里插入图片描述

3.4.2迭代器运算

在这里插入图片描述

3.5数组

3.5.1定义和初始化数组
int a[10];
int b[5] = {1,2,3,4,5};
int c[3] = {0};
3.5.2访问数组元素

下标访问

3.5.3指针和数组

跟C一样

使用数组初始化vector对象

int a[5] = {1,2,3,4,5};
std::vector<int> s(std::begin(a), std::end(a));
for(auto x : s) std::cout << x << '\n';

尽量使用标准库而非数组

3.6多维数组

与C基本相同

第四章 表达式

4.1基础

没什么可说的,略

4.2算术运算符

在这里插入图片描述
注意:C++的除法是向0取整

4.3逻辑和关系运算符

在这里插入图片描述

4.4赋值运算符

=

4.5递增和递减运算符

++--
注意区分前置和后置的区别

int i = 0;
int a[3] = {1, 2, 3};
std::cout << a[++ i] << '\n';	//输出为2
i = 0;
std::cout << a[i ++] << '\n';	//输出为1,但i也变成了1

4.6成员访问运算符

.->

4.7条件运算符

? :,三目运算符

4.8位运算符

在这里插入图片描述

4.9sizeof运算符

sizeof运算符返回一条表达式或一个类型名所占的字节数。

4.10逗号运算符

逗号运算符含有两个运算对象,按照从左向右的顺序依次求值。

4.11类型转换

隐式转换

int a = 3.14 + 3;	//a = 6, 3先转换成double与3.14相加,再转换成int

强制类型转换

double b = 3.14;
int a = (int)b;

4.12运算符优先级表

在这里插入图片描述
在这里插入图片描述

第五章 语句

5.1简单语句

语句;

5.2语句作用域

语句作用域是在一个花括号或者一个while、if、for的下一句

5.3条件语句

if (condition) {

}
else if (condition) {

}
else {

}

switch (x) {
	case condition1:
		statement1;
		break;
	case condition2:
		statement2;
		break;
	default:
		statement3;
		break;
}

5.4迭代语句

while (condition) {
	statement;
}

for (init-statement; condition; expression) {
	statement;
}

do {
	statement
} while (condition);

5.5跳转语句

break;	//终止与它相邻的最近的循环或者switch语句
continue;	//跳过与它相邻最近的循环

goto作用是从goto语句无条件跳转到同一函数内的另一条语句。
label是用于标识一条语句的标识符带标签语句是一种特殊的语句,在它之前有一个标识符以及一个冒号。

goto label;
end: return;	//带标签语句,可以作为goto的目标

以下代码输出4、5

int main() {
	int c = 1;
	if (c) {
		goto start;
	}


	printf("1\n");
	printf("2\n");
	printf("3\n");
start:	
	printf("4\n");
	printf("5\n");
}

5.6try语句块和异常处理

在C++中,异常处理包括:

  • throw表达式,异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发(raise)了异常。
  • try语句块,异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码
  • 一套异常类,用于在throw表达式和相关的catch子句之间传递异常的具体信息。

5.6.1 throw表达式

int a, b;
std::cin >> a >> b;

if(a != b) {
    throw std::runtime_error("a not equal b");
}

5.6.2 try语句块

try {
	program-statements
} catch (exception-declaration) {
	handler-statements
} catch (exception declaration) {
	handler-statements
}
try {
	if (a != b)
	    throw std::runtime_error("a not equal b");
} catch (std::runtime_error err) {
	std::cout << "请输入正确的数字\n";
}

5.6.3标准异常
在这里插入图片描述

第六章 函数

6.1函数基础

函数形式

int fac(int n);	//函数声明

int main() {
	
	std::cout << fac(5);
	
	return 0;
}

int fac(int n) {	//计算阶乘
	if(n <= 1) return 1;
	return n * fac(n - 1);
}

局部静态对象static函数结束时也不会销毁,直到程序结束时才销毁。

下边这几个忘记保存了,直接去世,等等再补充

6.2参数传递

当形参是引用时,我们说它对应的实参被引用传递或者函数被传引用调用。当实参的值被拷贝传递给形参时,我们说这样的实参被值传递或者函数被传值调用
形参是拷贝,实参是本身

数组形参

void print(int *a);
void print(int a[]);
void print(int a[10]);	//这里的维度表示我们期望数组含有多少元素,实际不一定
void print(int (&a)[10]);	//数组引用形参,a是具有10个整数的整型数组的引用
void print(int &a[10]);		//将a声明成了引用的数组

多维数组

void print(int a[][10]);
void print(int *a[10]);	//10个指针构成的数组
void print(int (*a)[10]);	//指向含有10个整数的数组的指针

含有可变形参的函数
initializer_list,所有实参类型相同时使用。(感觉跟数组一样)不同时写可变参数模板

void f(initializer_list<string> s) {
	for(auto beg = s.begin(); beg != s.end(); ++ beg)
		std::cout << *beg << ' ';
	std::cout << '\n';
}

省略符形参

void f(int a, int b, ...);

6.3返回类型和return语句

不要返回局部对象的引用或指针:局部对象用完就会被销毁,返回其引用或指针无意义。

列表初始化返回值

vector<int> f() {
	return {1, 2, 3};
}

返回数组或指针

int a[10];
int *p1[10];	//含有十个指针的数组
int (*p2)[10] = &a;	//p2是一个指针,它指向含有10个整数的数组

int (*f(int i)) [10];	//返回值是指向数组的指针
  • f(int i)表示调用f时需要一个int类型形参
  • (*f(int i))意味着我们可以对函数调用结果进行解引用操作
  • (*f(int i))[10]表示解引用f得到的是一个大小是10的数组
  • int (*f(int i))[10]表示数组中的元素是int类型

使用尾置返回类型
auto f(int i) -> int(*)[10]func接收一个int类型的实参,返回一个指针,指向还有10个整数的数组

6.4 函数重载

对于重载函数来说,它们应该在形参数量或形参类型上有所不同,不允许两个函数除了返回值类型外其它所有要素都相同。

重载和const形参
顶层const无法区分,底层const可以区分(顶层const是变量本身不可修改,底层const是变量本身可修改指向或使用的对象不可修改)

int f(int a);
int f(const int a);	//重复声明

int f(int *a);
int f(const int *a);	//新函数,a的指向可以修改,不能通过a修改其指向对象,所以是一个底层const

调用重载函数
函数匹配是指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫作重载确定
调用重载函数时有三种可能的结果:

  • 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码。
  • 找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息。
  • 有多于一个函数可以匹配,但是每一个都不是最优选择。此时也将发生错误称为二义性调用

重载与作用域
一旦在当前作用域中找到所需的名字,编译器就会自动忽略掉外层作用域中的同名实体。
在C++中名字查找发生在类型检查之前

void print(int a);

void f() {
	void print(string a);	//新作用域,隐藏了之前的print
	print(3);	//错误void print (int a)被隐藏
	print("Value");	//正确
}

6.5特殊用途语言特性

默认实参
一旦某个形参被赋予了默认值,它后边的所有形参都必须有默认值
函数声明和函数体的相同形参只能有一个有默认实参

#include<bits/stdc++.h>

void print(int a, int b, int c = 100);

int main() {
	print(1, 2);
	return 0;
}

void print(int a, int b, int c) {
	std::cout << c << '\n';
}

内联函数和constexpr函数

内联函数可避免函数调用的开销
将函数指定为内联函数,通常就是将它在每个调用点上“内联”地展开。

#include<bits/stdc++.h>

inline int f(int t) {
	return t >= 0 ? 10 : -10;
}

int main() {
	std::cout << f(1) << '\n';	//在编译时展开成 1 >= 0 ? 10 : -10;
	return 0;
}

constexpr函数
constexpr函数是指能用于常量表达式的函数。定义constexpr函数的方法与其它函数类似,不过要遵循几项约定:函数的返回类型 及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句

调试帮助
两项预处理功能:assertNDEBUG

assert预处理宏
assert是一种预处理宏。所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的 条件:
assert (expr);
首先对expr求值,如果表达式为假,assert输出信息并终止程序的执行,如果表达式为真assert什么都不做。
assert宏定义在cassert头文件中。

NDEBUG预处理变量
assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。
仔细阅读《C++ primer》P216

6.6函数匹配

#include<bits/stdc++.h>

void f() {}
void f(int ) {}
void f(int, int) {}
void f(double, double = 3.14) {}


int main() {
	f(5.6);
	return 0;
}

四个函数均为候选函数,因为函数名相同。第二个和第四个为可行函数因为参数数量相同并且实参可以转换为相应类型的形参。最后编译器选择了第四个函数,因为f(5.6)调用第二个函数需要将double转换为int,调用第四个函数不需转换是最佳匹配函数。

如果有且只有一个函数符合以下条件则匹配成功:

  • 该函数每个实参的匹配都不劣于其它可行函数需要的匹配
  • 至少有一个实参的匹配优于其它可行函数提供的匹配
    如果没有一个函数脱颖而出,则该调用是错误的。编译器将报告二义性调用的信息。
    建议不要炫技…

6.7函数指针

函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回值类型和形参类型共同决定,与函数名无关。

#include<bits/stdc++.h>

bool f(int a, int b) {
	return a > b;
}

int main() {

	bool (*p)(int a, int b);	//未初始化的函数指针

	//使用函数指针
	p = f;
	bool flag = p(3, 4);
	std::cout << flag << '\n';
	
	return 0;
}

重载函数的指针
指针类型必须与重载函数中的一个精确匹配

第七章 类

类的基本思想是数据抽象封装。数据抽象是一种依赖于接口实现分离的编程技术。

7.1定义抽象数据类型

struct Sales_data {

	//关于Sales_data对象的操作
	std::string isbn() const {
		return bookNo;
	}

	Sales_data &combine(const Sales_data &);
	double ave_price() const;

	//类的数据成员
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;
};

//Sales_data的非成员接口函数
Sales_data add(const Sales_data &a, const Sales_data &b){

}
std::ostream &print(std::ostream &cout, const Sales_data &b);
std::istream &read(std::istream &cin, const Sales_data &b);

定义在类内部的函数是隐式的inline函数

引入this
在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看作this的隐式引用。

std::string isbn() const {
	return bookNo;
}
//可以写为
std::string isbn() const {
	return this->bookNo;
}

因为this的目的总是指向“这个”对象,所以this是一个指针常量(书上说是常量指针,但查了很多地方貌似都叫指针常量emmm)。

引入const成员函数
isbn()函数后跟一个const,这里const的作用是修改隐式this指针的类型,表示this是一个指向常量的指针,像这样使用const的成员函数被称作常量成员函数,它不能修改类中的成员。

类作用域和成员函数
类外定义成员函数需要加上作用域

double Sales_data::ave_price() const{
	if(units_sold)
		return revenue / units_sold;
	return 0;
}

返回一个this对象的函数

Sales_data &Sales_data::combine(const Sales_data &a) {
	units_sold += a.units_sold;
	revenue += a.revenue;
	return *this;
}

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

std::ostream &print(std::ostream &cout, const Sales_data &item) {
	
	cout << item.isbn() << " " << item.units_sold << " "
		<< item.revenue << " " << item.ave_price();
	return cout;
}

std::istream &read(std::istream &cin, Sales_data &item) {
	double price = 0;
	cin >> item.bookNo >> item.units_sold >> price;
	item.revenue = price * item.units_sold;
	return cin;
}

定义add函数

Sales_data add(const Sales_data &a, const Sales_data &b){
	Sales_data sum = a;
	sum.combine(b);
	return sum;
}

构造函数
类通过一个或几个特殊的成员函数来控制其对象初始化的过程,这些函数叫做构造函数
构造函数的名字和类相同,没有返回类型。构造函数也可以重载,不同构造函数之间必须在参数数量或参数类型上有所区别。构造函数不能被声明成const的。
Sales_data类的构造函数

Sales_data() {
	bookNo = "";
	units_sold = 0;
	revenue = 0;
}

合成的默认构造函数
类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数。默认构造函数无须任何实参。
如果我们的类没有显示的定义一个构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。
编译器创建的构造函数又称为合成的默认构造函数

构造函数

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) {}

default含义:因为我们已经有构造函数了,但我们还需要默认构造函数,所以在参数列表后面加上=default告诉编译器我们需要默认构造函数。
第二、三条是列表初始化,函数与花括号之间的叫做构造函数初始值表

拷贝、赋值和析构

Sales_data total, trans;
total = trans;
//等价于
total.bookNo = trans.BookNo;
...
...

编译器会为我们完成这些操作,但有时需要自己实现(在后边的章节讲解析构函数)

7.2访问控制与封装

在C++中我们使用访问说明符加强类的封装性。

  • 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
  • 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。
class p {
public:
	/*
		具体实现
	*/
private:
	/*
		具体实现
	*/
};

class与struct的区别
两者唯一的一点区别是,struct和class的默认访问权限不同。struct默认权限是public,class默认权限是private

友元
非成员函数想访问private怎么办呢?直接在类内声明前加friend使其变成类的友元,这样就能访问private内的内容
友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。

7.3类的其它特性

可变数据成员
有时,我们希望能修改类的某个数据成员,即使是在一个const成员函数内。可以通过在变量的声明中加入mutable关键字做到这一点。
一个可变数据成员永远不会是const

返回*this的成员函数

inline Sales_data &Sales_data::test1() {
	return *this;
}

返回值一定要是引用!!!,如果返回值不是引用,将返回该对象的一个副本不是该对象本身,如果函数中间有行为,则只能改变副本,不能改变原对象

类的声明class T;

类之间的友元关系

将t2设置为t1的友元,t2的成员函数就可以修改t1中private部分的内容,如果想令某个成员函数作为友元,只需要将friend class t2;换成friend void t2::modify(t1&)类似这样的即可,即friend + 函数名

#include<bits/stdc++.h>

using namespace std;

class t1{

	friend class t2;

public:

	void print() {
		cout << t;
	}

private:

	int t = 0;

};

class t2{

public:
	void modify(t1&);

private:
	int t2 = 0;
};

void t2::modify(t1 &x) {
	x.t = 10;
}

void test() {
	t2 x;
	t1 y;
	x.modify(y);
	y.print();
}

int main() {

	test();

	return 0;
}

7.4类的作用域

感觉这部分没什么可说的QAQ

7.5构造函数再探

构造函数的初始化列表

class X {
	int i, j;
public:
	X(int val) : j(val), i(j) {}
};

上面这种方法是错误的,实际上i先被未定义的j初始化。
这样就好了

class X {

	int i, j;
public:
	X(int val) : j(val), i(val) {}
};

委托构造函数

class X {

public:
	//正常构造函数
	X(string S, int Cnt, int P) : s(S), cnt(Cnt), p(P) {}

	//委托构造函数,其实就是用来上面的函数emmm
	X() : X("", 0, 0) {}
	X(string S) : X(S, 0, 0) {}


private:

	string s;
	int cnt;
	double p;
};

隐式的类类型转换
如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时我们把这种构造函数称作转换构造函数

在这里插入图片描述
如图所示,在调用item.combine(null_book);时null_book可以自动转换为Sales_data类型,之后执行该操作

怎么防止隐式类类型转换发生?
explicit关键字!

在这里插入图片描述
这样就不会发生隐式类类型转换,而需要程序员显式的转换/定义。
构造函数使用explicit关键字后只能用于直接初始化
在这里插入图片描述
聚合类
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。当一个类满足如下条件时,我们说它是聚合的:

  • 所有成员都是public的。
  • 没有定义任何构造函数。
  • 没有类内初始值。
  • 没有基类(即父类),也没有virtual函数。(关于虚函数,在之后的章节里)

可以直接使用初始化列表的形式初始化聚合类。

struct Node {
	int x, y, z;
};

Node A = {1, 2, 3};

字面值常量类
某些类也是字面值类型,和其他类不同,字面值类星的类可能含有constexpr函数成员。这样的成员必须符合constexpr函数的所有要求,它们是隐式const的。
数据成员都是字面值类型的聚合类是字面值常量类。如果不是聚合类,但满足以下要求,则它也是一个字面值常量类:

  • 数据成员必须都是字面值类型
  • 类必须至少含有一个constexpr构造函数
  • 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。
  • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

constexpr构造函数
尽管构造函数不能是const的,但字面值常量类的构造函数可以是constexpr函数。事实上,一个字面值常量类必须至少提供一个constexpr构造函数。
在这里插入图片描述

7.6类的静态成员

有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。

声明静态成员
我们通过在成员的声明之前加上关键字static使得其与类关联在一起。静态成员函数不于任何对象绑定在一起,它们不包含this指针。

class Account {
public:
	void calculate() {
		amount += amount * interestRate;
	}

	static double rate() {
		return interestRate;
	}

	static void rate(double);

private:
	string owner;
	double amount;
	static double interestRate;
	static double initRate();
};

使用类的静态成员

double r;
r = Account::rate();

虽然静态成员不属于类的某个对象,但是我们仍然可以使用类的对象、引用或指针来访问静态成员。

Account ac1;
	ac1.rate();

成员函数不用通过作用域运算符就能直接访问静态成员。
当在类外定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句。

静态成员可以是不完全类型

class Account {
public:


private:
	static Account a;	//正确,静态成员可以是不完全类型
	Account *b;	//正确,指针可以是不完全类型
	Account c;	//错误数据成员必须是完全类型
};

第II部分

我的C++ primer笔记(第II部分)

  • 13
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值