栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 随记三 (一些杂项)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 随记三 (一些杂项)

1、使用只读算法时,传参要注意:


比如,accumulate就是个只读函数:

string sum = accumulate(s.cbegin(), s.end(), string(""));

插一嘴,最后一个参数必须是string(""),不能只写一个空字符串""上去,因为第三个参数的类型决定accumulate函数调用哪种类型的+运算符,而const char*没有+运算符,会产生编译错误。

2、使用equal的注意事项 equal不能比较两个【C风格的字符串】是否相等。

因为C风格的字符串本质是char*,是【指针】,用equal比较时只检查指针是否相等,即地址是否相同,不会比较其中的字符是否相同。

3、(泛型)算法不改变容器的大小

泛型算法本身不会执行容器的迭代器,它可以在容器内移动元素,但永远不会直接添加或删除元素。
所以在调用一些算法(尤其是写容器的算法)时,一定要确保 【容器的大小】 大于 【写入元素】的数量。
比如调用fill_n函数时:

vector v;					空的vector容器
fill_n(v.begin(), 10, 0);		想要写入10个0

上面的语句想写入10个元素,但是v是空的,结果是未定义的。
但C++提供了一种插入迭代器,使得以上操作成为可能。

使用back_inserter创建插入迭代器,它接受一个容器 或容器的引用,返回指向容器末尾的迭代器。
通过此迭代器赋值时,赋值运算符会【调用push_back】。

vector v;
fill_n(back_inserter(v), 10, 0);

在每步迭代中,fill_n向给定序列的一个元素赋值。由于我们传递的参数是back_inserter返回的迭代器,因此每次赋值都会在v上调用push_back。

4、unique的实现方式不是删除重复值,而是将他们交换到了容器默认 5、如何把 字符串去重,并按长度排序,长度相同的按字典序排

先去重,去重的过程中会完成“粗暴的”字典排序,也就是:长度是乱的要想按长度排,需要自己写isshorter函数(自命名的名字),作为【谓词】传入stable_sort函数

注意:第二步,不能调用sort函数,因为sort函数无法保证对于长度一样的串,再按字典排,也就是不稳定的,而stable_sort是稳定排序,长度相同还能保持原来的位置。

6、lambda表达式 6.1 lambda的作用

lambda表达式可以让我们把函数当作【数据】一样传递,使我们从繁琐的语法中解放出来,更加关注于 “算法” 本身。
lambda表达式完全可以取代谓词。

6.2 使用lambda表达式有如下几点限制:

捕获列表只用于局部非静态变量。lambda可以直接使用定义在当前函数之外的名字,所以可以直接使用cout 6.3 捕获列表:

[]不捕获任何变量,这种情况下lambda表达式内部不能访问外部的变量。
[&]以引用方式捕获所有变量
[=] 用值的方式捕获所有变量(可能被编译器优化为const &
[=, &foo]以引用捕获变量foo, 但其余变量都靠值捕获
[&, foo] 以值捕获foo, 但其余变量都靠引用捕获
[bar]以值方式捕获bar; 不捕获其它变量
[this]捕获所在类的this指针 (Qt中使用很多,如此lambda可以通过this访问界面控件的数据)

如果想改变以值的方式捕获的变量的话,要用mutable关键字,如下图所示:

int x = 1;
auto func = [x] () mutable { return ++x; };    此时,lambda的参数列表的一对括号()就不能省略了
cout << func() << endl;
6.4 注意事项

当以引用,指针或迭代器方式捕获一个局部变量时,必须保证调用lambda时,他们都还存在,否则会出错。值捕获就不存在这种问题,因为,只是保存了一个拷贝,永远都不用担心捕获的值没了。一般来说,应尽量减少捕获的数据量,避免潜在的捕获导致的问题。而且应尽量避免捕获指针或引用。 7、二维数组做函数参数

在调用的时候直接把数组名字作为实参传进去就行函数在声明和定义的时候必须要规定好二维数组的列数,如下图所示:而且N必须是个【常量】 8、transform函数

作用:对范围内的元素进行传入的操作。

std::transform(Name.begin(), Name.end(), Name.begin(), ::toupper);

前两个参数限定了进行操作的元素范围,第三个参数是存放结果的起始位置,第四个参数是操作,可以是一个lambda表达式。

9、bind重排参数的妙用
按单词长度【由短至长】排序
sort(words.begin(), words.end(), isShorter);
按单词长度【由长至短】排序
sort(words.begin(). words.end(), bind(isShorter, _2, _1));
10、bind函数需注意:非占位符参数是直接拷贝的

所以,当必须传引用时(如cout),需要用ref来传。如下面的例子所示:

ostream& print(ostream& os, const string& s);
for_each(s.begin(), s.end(), bind(print, cout, _1);				错误,不能拷贝一个ostream
for_each(s.begin(), s.end(), bind(print, ref(cout), _1);		正确

在bind里要想传递一个对象而又不拷贝他,只能使用标准库函数ref。

目前bind有什么用呢?

答:为替代lambda提供一个办法。当lambda不捕获局部变量时,用函数替代它很容易,但当lambda捕获局部变量时就不行了,这时,bind就能做到了。

bind接受几个参数??? bind是可变参数的。它接受的第一个参数是一个【可调用对象】,即实际工作函数A,返回【供算法使用】的【新的可调用对象B】。若A接受x个参数,则bind的参数个数应该是x+1,即除了A外,其他参数应【一一对应】A所接受的参数。这些参数中有【一部分来自于B(_n)】,另外一些来自于【所处函数的局部变量】。

不要把调用bind所返回的可调用对象 和 bind搞混了啊!Kora-a–a—a!!!
find函数返回的可调用对象就是一个函数适配器,它是通过底层存在的原函数修改参数(修改、增加等)变成的新的函数。

11、accumulate函数

功能,计算一段区间内所有值的和。
参数:两个迭代器,表示容器的一段区间,以及一个sum的初值,最后返回sum + 区间内所有元素的和

12、copy的第三个参数 说几遍才能记住???普通迭代器不能改变容器大小,所以第三个参数能是v.begin()吗????

记住了,要传一个back_inserter进去。

13、几种内存空间 都存了什么

静态内存:局部static对象,类的static数据成员,定义在函数外的变量(全局变量)栈 内 存 :定义在函数内的非static对象堆:我们自由使用,这是每个程序都拥有的内存池。 14、initializer_list类型

initializer_list类型用于向函数中传入【可变数量】的实参,但这些形参的【类型都得相同】。

initializer_list中的元素永远是常量,无法改变 使用案例:

定义一个用到 initializer_list 的函数:		(该函数用于输出不同的错误信息)
void error_msg(initializer_list il)
{
	for (auto beg = il.begin(); beg != il.end(); beg++)
		cout << *beg << " ";
	cout << endl;
}

向该函数传入不同数量的形参:(expected 和 actual 都是string类型)
if (expected != actual)
	error_msg( { "functionX", expected, actual} );
else
	error_msg( { "functionX", "okey" } );
15、使用友元时发生:类型未定义
#include

class X;	提一嘴,本行声明一个类而不定义它,被称为  向前声明 (forward declaration)

void func(X x) { cout << x.member << endl; }

class X
{
	friend void func(X x);
private:
	int member;
};

int main()
{
	X x;
	func(x);
}
上面的代码就会报出:“X”类型未定义的错误?这是为什么??我不是在前面声明了类X吗??

不不不,虽说你声明了类X,但是编译器到此时对X还一无所知啊。你在func里使用的member是什么东西???编译器根本不知道,所以说类型未定义。

做了类的声明以后,系统只知道有这样一个类,【并不知道有什么成员】。这时,在这个类的【声明和定义】之间就只能使用这个类型的【引用】或【指针】
在声明之后,定义之前,类是一个不完全类型(incompete type),即,已知向前声明过的类是一个类型,但不知道包含哪些成员。不完全类型只能以有限制的方式使用。 不能定义该类型的对象,不完全类型【只能用于定义指向该类型的指针和引用】,或者用于【声明】使用该类型作为【形参类型】或【返回类型】的函数。

改正上述代码:
改法一:

#include

class X;

void func(X x);					这里只放个函数的声明,函数声明离职出现了类名,不会用到X的成员

class X
{
	friend void func(X x);
private:
	int member;
};

void func(X x) { cout << x.member << endl; }	我把函数定义放在这里,就没问题了,X都定义好了

int main()
{
	X x;
	func(x);
}

改法二:

#include

class X;

class X
{
	friend void func(X x);
private:
	int member;
};

void func(X x) { cout << x.member << endl; }		我直接把函数的声明和定义都拿下来

int main()
{
	X x;
	func(x);
}

注意!!!!!!!!!!!!!!!!!下面这样是不行的:

#include

class X;

class X
{
	friend void func(X x) { cout << x.member << endl; }我直接声明友元的时候定义喽它,怎么样?
private:												必不行!!!!!!!
	int member;
};

int main()
{
	X x;
	func(x);
}

虽说,可以在友元声明的时候顺便把函数定义了,但是 友元声明永远不能作为一个函数或类的声明,他只告诉编译器这是友元。即使你已经在此时此地把他给定义了。 后面想要使用该友元,必须在类外进行 单独声明

16、良好编程习惯(结合上一条) 先把类定义好后,再写函数实现部分 17、单独编译
可以通过编译的源文件就是一个编译单元,一个程序,可以由一个编译单元组成,也可以有多个编译单元组成。如:我们可以将所有东西都放在一个.cpp文件内,然后编译器就将这个.cpp编译成.obj,这就是一个编译单元。 c++允许程序员将组件函数放在独立的文件中,可以单独编译这些文件,然后将它们链接成可执行的程序。 一个 .cpp 对应一个 .obj,然后将所有的 .obj 链接起来(通过一个叫链接器的程序),组成一个.exe ,也就是程序了。 如果一个.cpp要用到另一个.cpp定义的函数,只需在这个.cpp文件中写上要用的函数声明。(C++在同一个项目下的不同文件都位于全局作用域下,在其他文件中也可以引用)
18、动态数组并不是数组类型 19、make_plural函数

此函数首次出自《C++Primer 第四版》,是作者自定义的一个函数,不是哪个库里面的。

函数原型:

string make_plural(size_t ctr, const string& word , const string& ending)
{
	return ctr == 1 ? word : word + ending;
}

就是检查第一个参数是否是1,是1返回第二个参数,不是1返回第二个和第三个参数连起来的字符串。

20、记一个折磨半天,令我很生气的小错误
void xxx(iostream& is)
{
	string line;
	while (getline(is, line))	这行一直报错,没有与之匹配的重载
	{
		//................
	}
}

wdnmd,原来是因为ifstream不是在头文件iostream里面。焯!!气死我了,浪费半个小时!!!

文件输入输出流定义在fstream里面
字符串输入输出流定义在sstream里面

21、出现:【不允许使用不完整的类型】错误,一般都是头文件没有include全
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/768132.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号