C++每日一记!
每天学一点点C++!距离精通C++还有114514天!
包含了《Effective C++》、《C++primer》、《Effective STL》、《深度探索C++对象模型》、《深入理解C指针》、《凌波微步》、《C编译器剖析》的笔记和写代码与思考中遇到的问题。。。努力阅读中。。。
7/4
- 标准库定义了4个l0对象。为了处理输入,我们使用一个名为cin(发音为see-in)的istream类型的对象。这个对象也被称为标准输入(standardinput)。对于输出,我们使用一个名为cout(发音为see-out)的ostream类型的对象。此对象也被称为标准输出(standard output)。标准库还定义了其他两个ostream对象,名为cerr和clog(发音分别为see-err和see-log)。我们通常用cerr来输出警告和错误消息,因此它也被称为标准错误(standard error)。而clog用来输出程序运行时的一般性信息。
- IO对象无拷贝或者赋值
- 在函数参数括号写字符串被定义成const char *,最近经常碰到
- 局部静态对象只在第一次经过时初始化
- 当形参有顶层const时,传给它常量对象和非常量对象都是可以的
- 打算明天开始写项目多用指针和引用了,当时学算法不知道听了谁的鬼话尽量不用指针,结果现在还不熟。还是轮子哥说的靠谱。
8.1
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
istream& task(istream &a) {
string m;
while (a >> m) {
cout << m << endl;
}
a.clear();
return a;
}
int main()
{
std::filebuf in;
if (!in.open("E:/GitCode/Messy_Test/testdata/istream.data", std::ios::in)) {
std::cout << "fail to open file" << std::endl;
return;
}
istream wss(&in);
task(wss);
}
istream是所有输入类型的基类,直接cin调用即可。
6.2.1
#include "stdio.h"
void change(int* a, int* b) {
int c = *a;
*a = *b;
*b = c;
}
int main() {
int a, b;
a = 1;
b = 2;
int* c = &a;
int* d = &b;
change(c, d);
printf("a=%d b=%d",a,b);
}
6.2.2
#include "stdio.h"
void change(int& a, int& b) {
int c = a;
a = b;
b = c;
}
int main() {
int a, b;
a = 1;
b = 2;
change(a, b);
printf("a=%d b=%d", a, b);
}
6.2.3
#include "stdio.h"
#include <string>
std::string::size_type find_char(std::string& const s,char c,std::string::size_type &occurs) {
auto ret = s.size();
occurs = 0;
for (decltype(ret)i = 0; i != s.size(); i++) {
if (s[i] == c) {
if (ret == s.size()) {
ret = i;
}
++occurs;
}
}
return ret;
}
int main() {
std::string s = "sdafodsago";
std::string::size_type ctr;
auto index = find_char(s, 'o', ctr);
}
问题:我不明白一个课后问题,为什么std::string::size_type find_char(std::string& const s,char c,std::string::size_type &occurs)这个函数三个参数必须分别是现在的格式?或者说第一个参数用引用比不用引用好在哪里?(必须被定义为const的前提下)
明天打算重读引用那一章找出答案。
7/5
- 昨天问题的答案是:引用避免了新建对象,提高了速度
- 引用本身不是对象,不能定义引用的引用
- 如果你想要一个string形参能够接受常量,那么使用const;事实上,任何无需在函数中改变的字符串都应该被定义为常量引用
int *matrix[10]; //十个指针构成的数组
int (*matrix)[10]; //指向含有十个整数的数组的指针
- const cast用于改变const属性
- 一旦函数的某个形参被赋予了默认值,它后面的参数必须都有默认值
- 内敛函数可以在调用点内联展开,适用于优化流程直接规模小调用频繁的函数。
- constexpr函数是指能用于常量表达式的函数,它被隐式地指定为内联函数
- constexpr函数是指能用于常量表达式的函数:函数的返回值类型和所有形参的类型必须是“字面值类型”:算术、引用、指针。并且函数体内有且只有一条return语句。
6.2.4
#include "stdio.h"
#include<iostream>
#include<string>
using namespace std;
int function(int a,int *b,int *c) {
std::cout<< *c++;
std::cout << *c++;
return 0;
}
void error_msg(initializer_list<string> il) {
for (auto beg = il.begin(); beg != il.end(); beg++) {
cout << *beg;
}
}
void add(initializer_list<int> il) {
int sum = 0;
for (auto beg = il.begin(); beg != il.end(); beg++) {
sum += *beg;
}
cout << sum;
}
int main(int argc, char **argv) {
int a = 1,b = 2;
int *c = &b;
int j[2] = { 0,1 };
//function(a, c,j);
argv[0];
//std::cout << argv[1] << argv[2];
}
6.3.2
#include "stdio.h"
#include<iostream>
#include<string>
using namespace std;
char &get_val(string &str, string::size_type ix) {
return str[ix];
}
int main() {
string s("a value");
cout << s << endl;
get_val(s, 0) = 'A';
cout << s << endl;
return 0;
}
#include "stdio.h"
#include<iostream>
#include<string>
#include<vector>
#include <cstdlib>
using namespace std;
char &get_val(string &str,string::size_type ix) {
return str[ix];
}
int mainx() {
return 100000;
}
vector<string> process() {
return { "i","am","sb" };
}
int (*func(int i))[10]{
return 0;
}
//尾置返回类型
auto funcc(int i)->int(*)[10]{
}
//decltype
int odd[] = { 1,3,5,7,9 };
decltype(odd) *arrPtr(int i) {
}
string *testO(string &a) {
}
const string &shorterString(const string & s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
string &shorterString(string &s1, string& s2) {
}
int main() {
string a[] = {"a","b","c"};
testO(&a[]);
string s("a value");
cout << s << endl;
get_val(s, 0) = 'A';
cout << s << endl;
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
7/6
- assert(expr)预处理宏,如果表达式为0,输出信息并且终止程序执行;如果为真,则什么也不做。
- 预处理名字由预处理器而非编译器管理,因此不需要using声明。
- 宏名字必须在程序内唯一,含有cassert头文件的程序不能再定义名为assert的变量。
- 预处理器定义的五个程序调试很有用的名字(iostream)
__func__存放函数名
__FILE__存放文件名的字符串字面量
__LINE__存放当前行号的整型字面量
__TIME__存放文件编译时间的字符串字面量
__DATE__存放文件编译日期的字符串字面量
-
char在函数类型转换是对于int和short会匹配int
-
const类型转换:判别方式是判断实参是否是常量来决定选择哪个函数。指针类型的形参判别也是类似的。
-
类型提升:不是完全相同,皆会提升类型,char会提升为int。
-
算术类型转换:该转换的级别都相同,即3.14是double,转换成long或者float都是同样等级,会产生二义性。
-
·
typedef int(*p)(int a, int b);
声明函数指针,未初始化,p为指向函数的指针。使用typedef的声明语句定义的不再是变量而是类型别名就是将变量转化为类型别名的一种方式,p原来是指向函数的指针变量,现在变成了指向函数的指针变量的类型别名.
#include <iostream>
#include <string>
void print(const int ia[], size_t size) {
#ifndef NDEBUG
//__func__存放函数名
//__FILE__存放文件名的字符串字面量
//__LINE__存放当前行号的整型字面量
//__TIME__存放文件编译时间的字符串字面量
//__DATE__存放文件编译日期的字符串字面量
std::cerr << __func__ << ":array size is " << size << std::endl;
#endif // !1
}
int calc(int&, int&);
int calc( int& const, int& const);
bool(*pf)(const std::string &, const std::string &);
//pf=lengthCompare;
//pf=&lengthCompare;
void main() {
const int ia[3] = { 1,2,3 };
print(ia, 3);
}
7/7
考试,,其实是玩了一天dota2(悲)
7/8
- 在类的成员函数后面加const被称为常量成员函数,实质上是把函数隐式的this指针改变成了常量。常量对象以及常量对象的引用或指针都只能调用常量成员函数。
- 编译器首先编译成员的声明,其次编译成员函数体。因此成员函数体可以无视成员出现顺序调用。
- 使用=default来要求编译器生成构造函数
- 构造函数不能轻易覆盖类内初值,除非与原址不同,如果不能使用类内初始值,那么所有构造函数都应该显式地初始化每个内置成员
- struct和class定义类唯一的区别就是默认的访问权限。如果使用struct关键字,定义在第一个访问说明符之前的是public的,class则是private的。
- 知识点1:友元—类允许其他类或者函数访问其非共有成员,只要在本类内,加一条类前或者函数前有friend关键字(最前方)的声明即可。最好在类的开始或结尾集中声明友元。
优点:可以灵活地实现需要访问若干类的私有或受保护成员才能完成的任务,便于与其他不支持类的语言进行混合编程;通过使用友元函数重载可以更自然第使用C++语言的I/O流库。
缺点:一个类将对非公有成员的访问权授予其他的函数或类,会破坏该类的封装性,降低该类的可靠性和可维护性。 - 名字查找的顺序:由内而外、自上而下
- 类的定义处理步骤:首先编译成员的声明,直到类全部可见是再编译函数体
即成员函数在类的声明全部处理完之后才会被定义,所以可以调用函数中定义的任何名字 - 在类中,类型名要特殊处理,最好是定义在类的一开始,并且不能与外层作用域中同名
- Type有两种类型,应将最后一个函数的Type也声明为Exercise::作用域,因为返回值是double类型
#include <string>
#include <iostream>
struct Sales_data {
std::string bookNo;
std::string units_sold;
std::string revenue;
std::string isbn() const { return bookNo; }
//const作用是修改隐式this指针的类型
//std::string Sales_data::isbn(const Sales_data *const this) {
// return this->isbn;
//}
Sales_data& combine(const Sales_data &rhs);
};
Sales_data& Sales_data::combine(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, const Sales_data&);
std::istream &read(std::istream& is, const Sales_data& item){
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
}
Sales_data total(cin);
if (cin)
{
Sales_data trans(cin);
do
{
if (total.isbn() == trans.isbn())
total.combine(trans);
else
{
print(cout, total) << endl;
total = trans;
}
}while (read(cin, trans));
print(cout, total)<<endl;
}
else
{
cerr << "No data?!"<<endl;
}
7/9
做了一天OS课设,没看。。。
7/10
做了一天机械设计课设,还是没看。。。
7/11
- 编译器只会自动执行一步类型转换
- 使用explicit修饰构造函数可以阻止构造函数的隐式类型转换,并且只能用于直接初始化
- 对重载函数声明友元函数,需要对这组函数的每一个分别声明。
7/13
- 在类中不能重定义外层作用域中的名字,和一般情况相反。
- 必须通过构造函数初始值列表为const、引用的成员赋值
- 成员初始化顺序和它们在类定义中出现的顺序一致
- 一个良好的用户自定义类型的特征是它们避免无端的与内置类型不兼容。
7/14
- 缓冲刷新原因:1.main函数return操作的一部分 2.缓冲区满了 3.endl显式刷新缓冲区 4.操纵符unitbuf设置流的内部状态 5.读写被关联的流时,关联到的流的缓冲区会被刷新
- endl输出对象和换行符 flush输出对象 ends输出对象和空字符
- unibuf操作符:所有输出操作都会立即刷新缓存区
- 如果程序崩溃,输出缓冲区不会被刷新
- tie函数 不带参数的版本:返回指向输出流的指针/空指针 带参数的版本(指向输出流的指针),用于将自己关联到输出流
7/15
7/16
- 当一个fstream对象被销毁时,close会被自动调用
- file mode: in只读 out写 app每次写操作定义到文件末尾 ate打开文件立即定义到文件末尾 trunc截断文件 binary以二进制方式进行IO
- 无论哪种方式打开文件,都可以指定文件模式。调用open打开文件可以,用文件名初始化流隐式打开文件也可以。
7/17
- 如何避免拷贝行为?将copy构造函数和构造符重载函数声明为private
- 有趣的基类
class Uncopyable{
protected: //允许构造和析构
Uncopyable(){};
~Uncopyable(){};
private: //但是阻止copy
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
}
- 作为基类的函数其析构函数必须定义为virtual,否则子类析构容易造成删除不全面和内存泄露的问题
- 虚函数的作用是客制化
- 虚函数的实现:由一个vptr指针指向由函数指针构成的vtbl数组,决定运行期间哪一个virtual函数被调用
- 加入虚函数可能会使语言失去可移植性
7/18
- 对于一个给定流,每次打开文件时,都可以改变其文件模式
- 输出错误使用cerr而非cout避免重定向
- iostream处理控制台IO(后面两个继承自iostream) fstream处理命名文件IO stringstream完成内存string的IO
- 每个IO对象都维护一组条件状态,用来指出此对象是否可以进行IO操作,如果遇到了错误,状态变为失效直到错误被纠正
#include<iostream>
#include<string>
#include <fstream>
#include<string>
#include<sstream>
#include<vector>
using namespace std;
struct PersonInfo {
string name;
vector<string> phones;
};
int main() {
//写文件
ofstream out("test",ios::out | ios::trunc);
if (!out.is_open()) {
cerr << "file cannot open" << endl;
return -1;
}
out << "test start..." << endl;
out << 100 << endl;
out.put('c');
out.put('\n');
char buffer[1024] = "abc";
out.write(&buffer[0], sizeof(buffer));
out.close();
//string
string line, word;
vector<PersonInfo> people;
while (getline(cin,line)) {
PersonInfo info;
istringstream record(line);
record >> info.name;
while (record >> word)
info.phones.push_back(word);
people.push_back(info);
}
}
7/19
- begin和end必须构成一个迭代器范围
- difference_type是一种常用的迭代器型别,用来表示两个迭代器之间的距离
– begin和end成员函数有多个版本,带r的版本返回反向迭代器,带c的版本返回const iterator
– C c(d,e) 将c初始化为迭代器d,e之间元素的拷贝
– 创建一个容器为另一个容器的拷贝,必须要求类型相同;但是使用迭代器范围拷贝时,不要求类型相同。
– 新标准中的初始化列表隐式的指定了容器的大小
– 标准库array的大小是它类型的一部分 - int到底怎么转换为string?
std::string result;
int number = 12345;
char* buff;
itoa(number,buff,10);
result = std::string(buff);
但是流操作更简单:
#include <string>
#include <sstream>
#include <iostream>
int main() {
// std::stringstream 支持读写
std::stringstream stream;
std::string result;
int number = 12345;
stream << number; // 将 number 输入到 stream
stream >> results; // 从 stream 读取到 result
std::cout << result << std::endl; // 将输出为字符串"12345"
}
- std::unordered_map 就是无序容器其中之一,这个容器会计算元素的 Hash 值,并根据 Hash 值来判断元素是否相同。由于无序容器没有定义元素之间的顺序,仅靠 Hash 值来判断元素是否已经存在于容器中,所以遍历 std::unordered_map 时,结果是无序的。
- std::map 的遍历结果是有序的,而 std::unordered_map 的遍历结果是无序的。
事实上,std::unordered_map 在单个元素访问时,总是能够获得更高的性能。
#include <iostream>
#include <string>
#include <unordered_map>
#include <map>
int main() {
// 两组结构按同样的顺序初始化
std::unordered_map<int, std::string> u = {
{1, "1"},
{3, "3"},
{2, "2"}
};
std::map<int, std::string> v = {
{1, "1"},
{3, "3"},
{2, "2"}
};
// 分别对两组结构进行遍历
std::cout << "std::unordered_map" << std::endl;
for (const auto & n : u)
std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
std::cout << std::endl;
std::cout << "std::map" << std::endl;
for (const auto & n : v)
std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
}
- std::regex_match 用于匹配字符串和正则表达式,有很多不同的重载形式。最简单的一个形式就是传入std::string 以及一个 std::regex 进行匹配,当匹配成功时,会返回 true,否则返回 false。例如:
#include <iostream>
#include <string>
#include <regex>
int main() {
std::string fnames[] = { "foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt" };
// 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.`
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname : fnames)
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
- std::function 是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作,它也是对 C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),简而言之,std::function 就是函数的容器。
#include <functional>
#include <iostream>
int foo(int para) {
return para;
}
int main() {
// std::function 包装了一个返回值为 int, 参数为 int 的函数
std::function<int(int)> func = foo;
std::cout << func(10) << std::endl;
}
7/20
- 类模板的声明和定义要写在一个.hpp文件中,声明和定义不可以分开,因为分文件的话,main.cpp中只引入.h,在类模板进行二次编译的时候,无法找到类的定义,会出错。所以要将其声明和定义放到一个头文件中。类模板必须要写在.hpp文件中。
- 常量对象,以及常量对象的引用或指针都只能调用常量成员函数。在参数列表后加入const使this指针变成常量指针来引用常量对象;这样做能够防止const对象调用非const方法,导致成员变量被修改——这会引起报错,在报错之前阻止它。
- 总而言之,对于不修改成员变量的函数,尽量使用const
- 对象不存在用emplace_back,存在用push_back
7/21 - 7/25
梦符祭真好玩.jpg
我tm充爆
学车ing
7/26
- forward_list是单向列表,只能通过改变一个元素的后继来改变列表。
因此,forward_list只定义了insert_after emplace_after 和erase_after操作 - 使用reserve预分配空间
- size指的是容器已经保存的元素数目,capacity则是容器不增长的时候元素数目的大小
- 可以调用shrink_to_fit请求vector将超出目前大小的多余内存退回给系统
- 所有的capacity容器增长策略遵循一个原则:在push_back元素的时候,花费的时间不能超过其整数倍
- string搜索函数返回的是unsigned类型,不要使用带符号的类型保存它!
- 使用to_string转换为字符,使用stod转换为浮点数,
string s2 = "pi = 3.14";
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
- 一个适配器是一种机制,能使某种事物的行为看起来像另一种事物一样
//#include <boost/math/constants/constants.hpp>
//#include <boost/multiprecision/cpp_dec_float.hpp>
//#include <iostream>
//#include <iomanip>
//
//using namespace std;
//using namespace boost::math::constants;
//using namespace boost::multiprecision;
//
//int main()
//{
// cout << "圆周率是:" << setprecision(50) << pi<cpp_dec_float_50>() << endl;
// cout << "自然对数e是:" << setprecision(50) << e<cpp_dec_float_50>() << endl;
// cout << "根号2是:" << setprecision(50) << root_two<cpp_dec_float_50>() << endl;
// return 0;
//}
//// 圆周率π是:3.1415926535897932384626433832795028841971693993751
//// 自然对数e是:2.7182818284590452353602874713526624977572470937
//// 根号2是:1.4142135623730950488016887242096980785696718753769
#include<vector>
#include<iostream>
#include<sstream>
#include<fstream>
using namespace std;
int main() {
vector<int> ivec;
ivec.reserve(50);
cout << "ivec_size:" << ivec.size()
<< "ivec_capacity" << ivec.capacity()<<endl;
//输出0 50
ivec.shrink_to_fit();
cout << "ivec_size:" << ivec.size()
<< "ivec_capacity" << ivec.capacity() << endl;
//输出0 0
string s;
const char *cp = "stately,plump buck";
s.assign(cp, 7);
cout << s<< endl;
}
7/27
#include <string>
#include<iostream>
#include<windows.h>
class Base {
public:
std::string test = "base";
virtual void display(std::string strShow = "i an base class") {
std::cout << "11111"<<std::endl;
std::cout << strShow << std::endl;
}
};
class Derive :public Base {
public:
std::string test = "derive";
virtual void display(std::string strShow = "derive") {
std::cout << 22222 << std::endl;
std::cout << strShow << std::endl;
}
};
//void a(const std::string &p) {
// std::cout << "first";
//}
//void a(bool p) {
// std::cout << "second";
//}
int main() {
Base d1;
Base d3;
Derive d2;
Derive d4;
Base* pBase_ = new Base();
Base* pBase = new Derive();
Derive* pDerive = new Derive();
pBase->display();
pDerive->display();
std::cerr << pBase->test << std::endl;
std::cerr << pDerive->test << std::endl;
}
//
//class Base1{
//public:
// int base1_1;
// int base1_2;
//
// virtual void base1_fun1(){}
//
//};
//int main() {
// Base1* b1 = new Base1();
// std::cerr << sizeof(Base1) << std::endl;
// std::cerr << offsetof(Base1, base1_1) << std::endl;
// std::cerr << offsetof(Base1, base1_2) << std::endl;
//
// return 0;
//}
- 分析内存:注意是否使用同一个虚表
7/28
1.泛型算法是为容器库提供的一组操作用算法,大多数定义在头文件algorithm中。
例如:find(迭代器1,迭代器2,要寻找的值)
2.算法只能做出迭代器的操作而不能做出容器的操作,也就是说,泛型算法不能改变容器的大小。
3.拷贝算法copy能够用于内置类型比如Int的拷贝
7/29-8/1
学习boost库...
8/2
- pair提供一对key-value 而map提供多对key-value
- const用于修饰该函数,表示在函数内不能改变其对应对象的成员变量的值 还有对于隐式指针this转换常量的作用 这个问题出现好几次了!!
- bind标准库函数定义在头文件functional中 我们可以使用auto newCallable = bind(callable,arg_list)来生成一个新的可调用对象 bind函数是一个通用的函数适配器 使用_1 _2 _3等等作为占位符
8/15
- 定位new(placement new)允许我们向new传递额外的参数
int *p=new(nothrow)int;
- 使用reset来将一个新的指针赋予一个stared_ptr
- 使用unique检查是否是指向内存唯一的用户
- shared_ptr创建指针时,可以传递一个指向删除器函数的参数。确保连接被关闭。
- 小心使用get方法导致管理的指针被释放
- get方法:用来将指针的访问权限传递给代码
- decltype类型说明符,它的作用是选择并返回操作数的数据类型
- weak_ptr不改变引用计数,必须用stared_ptr来初始化
- 由new初始化的unique_ptr指针数组,会在指针销毁的时候自动调用delete[]
8/16
-
防止文件覆盖
ofstream fout(ifile,ios::app);
if (fout) {
cout << "success!" << endl;
const char* a = "Hello World!";
fout << a;
fout.close();
}
class TextQuery {
public:
using line_no = vector<string>::size_type;
//shared_ptr<vector<string>> a;
//map<string, shared_ptr<set<line_no>>> b;
TextQuery(ifstream& infile):file(new vector<string>) {
string text;
while (getline(infile, text)) {
file->push_back(text);
int n = file->size() - 1;
istringstream line(text);
string word;
while (line >> word) {
auto &lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
};
QueryResult query(string s) const;
private:
shared_ptr<vector<string>> file;
map<string, shared_ptr<set<line_no>>> wm;
};
- 如果已给构造函数第一个参数是自身类类型的引用,并且任何额外参数都有默认值,则此构造函数是拷贝构造函数
- 直接初始化 实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数;拷贝初始化要求将右侧运算对象拷贝到我们正在创建的对象中,如果需要的话还要进行类型转换
- 拷贝初始化不仅在用=定义变量时会发生,还会在下列情况中发生:
将一个对象作为实参传递给一个非引用类型的形参
从一个返回类型为非引用类型的函数返回一个对象
用花括号列表 初始化一个数组中的元素或一个聚合类中的成员 - 拷贝构造函数被用来初始化非引用类类型参数
- 如果一个类需要自定义析构函数,几乎也可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数
- 阻止拷贝:在拷贝构造函数和拷贝赋值运算符后面加上=delete来阻止拷贝
- 如果一个类有数据成员不能默认构造、拷贝、复制或者销毁,则对应的成员函数将被定义为删除的
8/17
- 右值引用指向将要被销毁的对象,使用右值引用的代码可以自由接管所引用的对象的资源
- move告诉编译器:我们有一个左值,但是我们希望像一个右值一样处理它。调用move就意味着:除了对rr1赋值或者销毁它之外,我们不再使用它
- noexcept是我们承诺一个函数不抛出异常的一种方法
- 移动后的源对象可能会在移动后被销毁,所以必须进入可析构的状态——将源对象的指针成员置为nullptr来实现
10/6
- C++中的函数签名(function signature):包含了一个函数的信息,包括函数名、参数类型、参数个数、顺序以及它所在的类和命名空间。普通函数签名并不包含函数返回值部分,如果两个函数仅仅只有函数返回值不同,那么系统是无法区分这两个函数的,此时编译器会提示语法错误。函数签名用于识别不同的函数,函数的名字只是函数签名的一部分。在编译器及链接器处理符号时,使用某种名称修饰的方法,使得每个函数签名对应一个修饰后名称(decorated name)。编译器在将C++源代码编译成目标文件时,会将函数和变量的名字进行修饰,形成符号名,也就是说,C++的源代码编译后的目标文件中所使用的符号名是相应的函数和变量的修饰后名称。C++编译器和链接器都使用符号来识别和处理函数和变量,所以对于不同函数签名的函数,即使函数名相同,编译器和链接器都认为它们是不同的函数。
不同的编译器厂商的名称修饰方法可能不同,所以不同的编译器对于同一个函数签名可能对应不同的修饰后名称。
For functions that are specializations of function templates, the signature includes the return type. For functions that are not specializations, the return type is not part of the signature.
A function signature consists of the function prototype. What it tells you is the general information about a function, its name, parameters, what scope it is in, and other miscellaneous information.
Two overloaded functions must not have the same signature.
Default Arguments: The last parameter or parameters in a function signature may be assigned a default argument, which means that the caller may leave out the argument when calling the function unless they want to specify some other value.
10/7
- string类型会默认结尾是一个空字符,如果一个string只含有一个空字符,则该string为空。
vector<char>
则不同,含有一个空字符后,就不在是空的。 #pragma pack
的主要作用就是改变编译器的内存对齐方式,这个指令在网络报文的处理中有着重要的作用,#pragma pack(n)是他最基本的用法,其作用是改变编译器的对齐方式, 不使用这条指令的情况下,编译器默认采取#pragma pack(8)也就是8字节的默认对齐方式,n值可以取(1, 2, 4, 8, 16) 中任意一值。- C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区)。全局数据区存放全局变量,静态数据和常量;所有类成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;余下的空间都被称为堆区。根据这个解释,我们可以得知在类的定义时,类成员函数是被放在代码区,而类的静态成员变量在类定义时就已经在全局数据区分配了内存,因而它是属于类的。对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的。
10/15
- c语言的垃圾回收机制将内存视为一张可达图,没有指针指向的部分被回收。
- 深入探究free与delete
free和delete的区别在哪里?delete会调用析构函数,malloc不会,确实。
free只能对malloc产生的指针进行操作,并且记录这个空间大小。
10/17
- 横向比较C++与Java的内存管理机制
今天写jvm,对于对象复制搞不明白,发现两个对象竟然指向同一块空间,在C++里面,我们创建对象放在栈区,不会随便用指针,也就是说jvm擅自用了指针,把两个对象指向了同一个内存空间!
我还是去看看《深入理解Java虚拟机》吧