C++ Primer 笔记

本文详细介绍了C++中的变量声明与定义的区别,以及extern关键字的使用。强调了引用的特性,它必须初始化并永久绑定到一个对象。此外,讨论了const成员函数和字符串的处理,包括初始化、读写、size()函数以及各种字符检查函数。还涵盖了重载运算符的设计原则,如赋值、下标、递增/递减、成员访问、函数调用运算符等。最后,提到了类型转换运算符和标准库的函数对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Chap02 变量

声明和定义的区别

如果想声明一个变量而非定义它, 就在变量名前加上extern

extern int i;	//声明i而非定义i
int j;			//声明并定义j

变量只能被定义一次, 但可以被多次声明。
如果要在多个文件中使用同一个变量,就必须将声明和定义分离,变量的定义必须且只能出现在一个文件中, 其他用到该变量的文件必须对其进行声明, 却绝不能重复定义。

引用

引用即别名,引用本身不是一个变量,故不能定义引用的引用。引用只能绑定在对象上。
引用必须初始化,并且会和它初始绑定的变量永久绑定,不能对引用再赋值。

我笔记呢?
???

const 位于类成员函数末尾 指 常量成员函数,不能改变类内成员

void function()const; //常成员函数, 它不改变对象的成员变量.

Chap 03 字符串、向量和数组

string

初始化

//直接初始化
string s("hiya");
string s1(10, 'c');
//拷贝初始化(=)
string s2 = "hiya";

string的读写

string s;
cin >> s;
// string对象会自动忽略开头的空白从第一个字符读起,直到遇到下一个空白。

getline

string line;
getline(cin, line);
// getline参数是一个输入流和一个string, 从输入流读取内容直到遇到换行符

string的size()函数
返回值是string::size_type类型, 是一个无符号整数

string对象和字面值相加

string s3 = "hello" + ", " + s2 + '\n';
//必须确保 + 两侧至少有一个是string
string s6 = s1 + ", " + "world";
string s7 = "hello" + ", " + s2;	//错误,不能把字面值直接相加
//切记 字面值 和 string 是不同的类型。

处理string中的字符

cctype头文件
isalnum(c)	// 字母或数字时为真
isalpha(c)
ispunct(c)	// 标点符号时为真
isdigit(c)
islower(c)
isupper(c)
tolower(c)
toupper(c)

访问string单个字符
[](下标运算符)返回的是该位置字符的引用
下标必须大于等于0且小于s.size() 超出范围会出现错误
同理,使用[]访问空字符串也会发生错误

Chap14 重载运算与类型转换

  • 当一个重载运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显式)参数数量比运算对象少一个。
    通常,不应该重载逗号、取地址、逻辑与(&&)和逻辑或(||)运算符

重载运算符应选择 : 成员函数 ?非成员函数 ?
有以下准则:
赋值 = 下标 [] 调用() 和成员访问 -> 运算符必须是成员。
复合赋值运算符一般是成员。
改变对象状态 或 与给定类型密切相关 如递增、递减、解引用通常是成员。
具有对称性的如算术、相等性通常应该是普通的非成员函数。
当我们把运算符定义成成员函数时,它左侧运算对象必须是运算符所属类的一个对象。

string u = "hi" + s;	//如果+是string的成员,则产生错误。

输入输出运算符 >> <<必须是非成员函数。
如果要为类自定义IO操作,必须将其声明为非成员函数。由于IO运算符通常要读写类的非公有成员,所以IO运算符通常被声明为友元。

算术和关系运算符

通常把算术和关系运算符定义成非成员函数,因其一般不改变运算对象的状态所以形参都是常量的引用。
如果类同时定义了算术运算符和复合赋值运算符,通常应使用复合赋值来实现算术运算符。

相等运算符

类通过定义相等运算符检验两个对象是否相等。定义了 == 也应定义 !=,并且其中一个应把工作委托给另一个做。

关系运算符

如果要定义 < 运算符,当且仅当 < 的定义与 == 产生的结果一致时才定义 < 运算符
(如对于Sales_data, isbn, unit_sold, revenue都可以比较,且比较的定义不一定,可能是unit_sold大更好,也可能小更好,这种情况不适合定义 < 运算符)

赋值运算符

除了拷贝赋值 移动赋值 运算符
标准库vector还定义了第三种赋值运算符,接受花括号内的元素列表作为参数

vector<string> v;
v = {"a", "an", "the"};

为了与内置类型赋值运算符保持一致,该运算符返回左侧运算对象的引用

Str &str::operator=(initializer_list<string> il)

无论形参的类型是什么,赋值运算符必须定义为成员函数
复合赋值运算符
不一定非得是类的成员,但还是倾向于把所有赋值运算符定义在类内。为与内置类型复合赋值一致,复合赋值运算符也返回左侧运算对象的引用。

下标运算符

下标运算符必须是成员函数, 通常以访问元素的引用作为返回值。
如果一个类包含下标运算符,通常定义两个版本:一个返回普通引用;一个是类常量成员返回常量引用。

递增和递减运算符

定义递增递减运算符的类应同时定义前置、后置版本。这些运算符应被定义成类的成员。
定义前置递增/递减运算符
为与内置版本一致,前置运算符应返回递增或递减后对象的引用。

StrBlobPtr& operator++();

如何区分前置和后置运算符
前置和后置使用同一符号,运算对象数量和类型也相同。
为解决此问题,后置版本接受一个额外(不被使用的)int形参。当使用后置运算符时,编译器提供值为0的实参,其作用就是区分前置后置,并不参与运算。

StrBlobPtr operator++(int);//用不到形参,故无须命名
{
	StrBlotPtr ret = *this;
	++*this;	//实际调用 前置++ 来完成 后置++
	return ret;
}

后置运算符应返回对象(递增递减前)原值,返回一个值而非引用。
后置运算符调用前置版本完成实际的工作。
显式调用后置运算符时,必须为它的整形参数传一个值。

StrBlobPtr p(a1);
p.operator++(0);

成员访问运算符

解引用 * 箭头 ->
箭头运算符必须是类的成员,解引用运算符一般也是。

std::string& operator*() conts {
	auto p = check(curr, "dereference past end");
	return (*p)[curr];
}

解引用首先检查curr是否在作用域,是,则返回curr所指元素的一个引用
箭头调用解引用

std::string* operator->() const {
	return & this->operator*();
}

这两个运算符都定义成const成员,因为获取一个元素不会改变对象的状态。
箭头运算符返回值的限定
箭头运算符不能丢失获取成员这个基本含义。
重载的箭头运算符必须返回 类的指针 或者 自定义了箭头运算符的某个类的对象

函数调用运算符

函数调用运算符必须是成员函数。一个类可定义多个不同版本函数调用运算符。
如果类重载了调用运算符,则可以像使用函数一样使用该类的对象。eg:

struct absInt {
	int operator() (int val) const {
		return val < 0 ? -val : val;
	}
}
//调用方式
int i = -42;
absInt absObj;
int ui = absObj(i);

虽然absObj只是个对象而非函数,我们也能**“调用”它。
如果类定义了调用运算符,则该类对象称为
函数对象(function object)**
函数对象常常作为泛型算法的实参

for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
//第三个参数是PrintString的一个临时对象,用cerr '\n'初始化
lambda是函数对象

当我们编写一个lambda后,编译器将其翻译成一个未命名类的未命名对象。在lambda产生的类中有一个重载的函数调用运算符。
默认lambda不能改变它捕获的变量,故默认情况下产生的类中的函数调用运算符是const成员函数。
如果lambda声明为可变,则函数调用运算符不是const了。

stable_sort(word.begin(), word.end(), 
			[]const string &a, const string &b) {
	return a.size() < b.size();					
}
//行为类似下面类的一个未命名对象
class ShorterString {
public:
	bool operator() (const string &s1, const string &s2) const
	{	return s1.size()  < s2.size();  }
}
//用类替代lambda后,可重写调用stable_sort
stable_sort(word.begin(), sort.end(), ShorterString());

表示lambda及相应捕获行为的类
lambda通过引用捕获变量时,程序确保引用所引对象存在,故编译器可直接使用引用无须在lambda产生的类中将其存储为数据成员。
而通过捕获的变量则拷贝到lambda中,lambda产生的类必须为每个值捕获变量建立对应数据成员,同时创建构造函数,使用其捕获的变量来初始化。

auto wc = find_if(word.begin(), word.end(), 
					[sz](const string &a))
		{	return a.size() >= sz	};
//产生的类形如:
class SizeComp {
	SizeComp(size_t n) : sz(n);	//该形参对应捕获的变量
	bool operator() (const string &s) const
	{	return s.size() >= sz	};
private:
	size_t sz;	//对应值捕获的变量
//合成类不含  默认构造函数、赋值运算符、默认析构函数
}
标准库定义的函数对象

标准库定义了一组类,分别定义一个执行命名的调用运算符,这些类被定义模板的形式,可为其指定具体应用类型,即调用运算符的形参类型。

//类型定义在functional头文件中
//算术					关系						逻辑
plus<Type>				equal_to<Type>			logical_and<Type>
minus<Type>				not_equal_to<Type>		logical_or<Typ>
multiplies<Type>		greater<Type>			logical_not<Type>
divides<Type>			greater_equal<Type>
modulus<Type>			less<Type>
negate<Type>			less_equal<Type>

在算法中使用标准库函数对象

sort(svec.begin(), svec.end(), greater<string>());

对于指针同样适用(意义不明)

可调用对象与function

意义不明,先放着

重载、类型转换与运算符(意义不明)

转换构造函数和类型转换运算符 共同定义了 类类型转换, 也称为用户定义的类型转换

类型转换运算符

是类一种特殊成员函数,将一个类类型的值转为其他类型

operator type() const;

类型转换运算符面向任何可作为函数返回类型的类型,不允许转换成数组或函数类型,但可以转换成指针(包括数组指针和函数指针)或引用类型。
类型转换函数必须是类成员函数,不能声明返回类型,形参列表必须为空,通常应是const

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值