重读C++ Primer笔记

C++ Primer 5E

有符号和无符号

无符号类型和有符号类型混合运算时,有符号数会被提升至无符号类型,如果其值为负责会产生错误。


int main()
{
unsigned int u = 10;
int i = -42;
std::cout<<u+i<< std::endl; // 4294967264 if sizeof(int)==4
return 0;
}

列表初始化

列表初始化过程不允许损失数据类型精度,所以下面代码中的两行无法通过编译


int main()
{
double d = 3.14;
int i1 = d;
//int i2 = { d }; //error
//int i3{ d }; //error
int i4(d);
return 0;
}

列表初始化可以用于直接初始化返回值。

函数内变量初始值

函数体内的基本类型局部变量如果没有初始化,则其值是未定义的。

constexpr与指针

constexpr修饰指针时,只能被初始化为nullptr、0或是在内存中位置固定的对象。而且 constexpr int
*q中constexpr修饰的是*,而不是int,这和const是不同的。

类型别名(type alias)

和typedef作用相同,但可以定义带模板参数的类型:

http://en.cppreference.com/w/cpp/language/type_alias


template<class T> using Vec = vector<T, Alloc<T>>;

auto和decltype

除了用法不同外,这两个的区别是,auto会去除顶层const(int *const中的const)和引用,而decltype不会。

decltype的结果和表达式的形式密切相关,decltype((variable))返回的结果永远是一个引用。对于下面数组两者的结果为


int a[10];
auto b(a); //int *b;
decltype(a) c; //int c[10];

默认实参的声明

默认实参不能重复定义,但可以进行增补:


int Fun(int a, int b, int c = 10);
int Fun(int a, int b, int c = 10); //error
int Fun(int a, int b = 10, int c); //ok

类作用域和定义在类外部的成员


struct Foo
{
typedef int T;
T Test();
};

T Foo::Test(){ return T();}//error 返回值类型T未知
Foo::T Foo::Test(){ return T();}//ok
auto Foo::Test() -> T { return T();}//C++11,ok

上述代码中,Foo::Test中的Foo::从Test开始生效,但前面的返回值不在此范围内,所以必须使用Foo::T的方式引用,或是使用C++11的返回值类型后置语法。

Literal Classes

Literal Classes是一种其实例可以成为constexpr对象的类型,成为Literal Classes的条件是:

1、 数据成员必须都是字面值类型(Literal type)

2、 类至少有一个constexpr构造函数。

3、 如果一个数据成员含有类内初始值,则:

a) 内置类型成员的初始值必须是一条常量表达式

b) 其他类型成员必须使用它自己的constexpr构造函数。

4、 类必须使用默认的析构函数。

Literal
Classes可以包含非constexpr的函数,也可以作为普通类型使用,但作为constexpr常量时,只能使用其constexpr的构造函数和成员函数。

Literal
Classes可以让constexpr常量在编译时初始化,参与编译时的优化,而不是像static那样推迟到程序启动时,这使得constexpr常量对象可以被放在程序的资源中。

Lambda表达式与捕获

Lambda表达式中值捕获后的变量在lambda中是带const修饰的,在参数列表后加mutable可以消除该效果,变成和普通函数一样可以修改传入的参数的值。


class Foo
{
public:
int value;
Foo(int v) : value(v){}
Foo(const Foo &o) : value(o.value){
printf("Copy init\n");
}

int inc()
{
return ++value;
}

int val() const
{
return value;
}
};

int main()
{
Foo a(3);
auto f1 = [=]() -> int { return a.val();};
printf("%d\n", a.value);
auto f2 = [=]() mutable -> int { return a.inc();};
printf("%d\n", a.value);
//auto f3 = [=]() -> int { return a.inc();}; //error
return 0;
}

运行结果为


Copy init
Call val
3
Copy init
Call inc
3

移动迭代器

一般的迭代器解引用返回左值,可以使用make_move_iterator将普通迭代器转换为移动迭代器,该迭代器解引用时返回右值。

右值和左值引用成员函数

和const一样,类的成员函数可以通过在参数列表后面添加
&或&&来限制该函数只能在左值或右值对象上调用,并可以通过这种方式进行重载,如


class Foo{
public:
Foo sorted() &&;
Foo sorted() const &;//不能写做& const
}

当成员函数中的重载函数有一个使用了&或&&时,其他重载函数也必须使用,如


class Foo{
public:
Foo sorted() &&;
Foo sorted() const ;//error
};

Static_cast与右值引用

Static_cast可以将左值引用转换为右值引用。

OOP相关

final关键字

final关键字可以阻止类被继承


class Foo final {};

继承的构造函数

C++11加入的,详见C++ Primer 5E 15.7.4节

一个类只能继承它的直接基类的构造函数,而且不能继承默认、拷贝和移动构造函数。语法为:


class Foo: public class Base
{
public:
using Base::Base; //继承Base的构造函数
int Fun();
};

其作为对于继承每个构造函数,编译器都为之生成对应的派生类构造函数,其形参列表与基类的构造函数完全相同。形如


derived(parms): base(args){}

如果派生类含有自己的数据成员,则这些成员将被默认初始化。(参见C++ Primer 5E 7.1.4节)

和普通成员的using声明不同,一个构造函数的using声明不会改变该构造函数的访问级别。而且using语句不能额外指定explicit或constexpr,生成的构造函数继承对应的基类构造函数的相应属性。

如果一个基类构造函数含有默认实参,这些默认实参不会被继承,而是生成多个构造函数,每个构造函数分别省略掉一个含有默认实参的形参。即基类的构造函数


int Foo(A a, B b = b0, C c = c0);

在基类中会生成下面三个构造函数


int Foo(A a, B b, C c);
int Foo(A a, B b);
int Foo(A a);

继承的构造函数不会作为用户定义的构造函数来使用,因此如果一个类只含有继承的构造函数,则它也将拥有一个编译器自动合成的默认构造函数。

在多继承的情况下,允许从多个直接基类继承构造函数,当有冲突出现时,必须定义派生类自己对应的版本。例如:


struct Base1
{
Base1() = default;
Base1(const std::string&);
Base1(int);
};

struct Base2
{
Base2() = default;
Base2(const std::string&);
Base2(const char*);
};

struct D1 : public Base1, Base2
{
using Base1::Base1;
using Base2::Base2;

D1(const std::string&);//这是必须的
D1() = default;//定义上一行后编译器不再产生默认的无参构造函数
};

派生类向基类转换的可访问性

只有当派生类D公有地继承自基类B时,才能使用派生类向基类的转换(指针、引用)。

友元与继承

友元关系不能被继承,基类的友元在访问派生类的成员时不具有特殊性,派生类的成员也不能随意访问基类的成员。但基类的友元可以访问派生类中基类的部分。如


class Base
{
protected:
int prot_mem;
friend class Pal;
};
class Sneaky : public Base
{
int j;
};
class Pa1
{
int f(Base b) { return b.prot_mem;} //ok
//int f2(Sneaky s) { return s.j; }// error
int f3(Sneaky s) { return s.prot_mem;} //ok
};
class D2 : public Pal
{
//int mem(Base b) { return b.prot_mem; }//error
};

虚析构函数与移动构造函数

虚析构函数会阻止编译器为其生成默认的移动构造函数,即使使用=default指定也不会生成。

构造和析构过程中调用虚函数

在构造函数和析构函数中,调用虚函数时,调用的是该构造函数/析构函数所在的继承层级中所定义的虚函数。即,在B-D1-D2这样的继承链中,D1的构造函数和析构函数调用虚函数时调用的是D1自己或B的虚函数版本,而不会是D2的。

多重继承与指针转换

多继承时,派生类指针向非第一个基类的转换会导致指针的值发生变化。


class Base1
{
int v1;
};

class Base2
{
int v2;
};

class D1 : public Base1, Base2
{
int v3;
};

int main()
{
D1 *d1 = new D1();
std::cout << ((int)(Base1*)d1) - ((int)d1) << std::endl; //0;
std::cout << ((int)(Base2*)d1) - ((int)d1) << std::endl; //sizeof(int)
return 0;
}

重读C++ Primer笔记,码迷,mamicode.com

时间: 2024-10-10 23:05:10

重读C++ Primer笔记的相关文章

C++ Primer笔记8_动态内存_智能指针

1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete.C语言中通过malloc与free函数来实现先动态内存的分配与释放.C++中new与delete的实现其实会调用malloc与free. new分配: 分配变量空间: int *a = new int; // 不初始化 int *b = new int(10); //初始化为10 string *str = new string(10, ); 分配数组空间: int *arr = new int[10];//分配的

C++ Primer笔记12_运算符重载_递增递减运算符_成员访问运算符

1.递增递减运算符 C++语言并不要求递增递减运算符必须是类的成员.但是因为他们改变的正好是所操作对象的状态,所以建议设定为成员函数. 对于递增与递减运算符来说,有前置与后置两个版本,因此,我们应该为类定义两个版本的递增与递减运算符. 问题来了,程序是如何区分前置和后置呢?因为都是++和-- 为了解决这个问题,后置版本的递增递减运算符接受一个额外的(不被使用)int类型的形参.当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参.这个形参唯一的作用就是区分前置和后置运算符函数. 因为不会

C++ Primer笔记2_四种类型转换_异常机制

1.类型转换 命名的强制类型转换: 有static_cast.dynamic_cast.const_cast.reinterpret_cast static_cast: 编译器隐式执行的任何类型转换都可以由static_cast完成 当一个较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换. 可以将void*指针转换为某一类型的指针 可以将基类指针强制转换为派生类指针,但是不安全. 无法将const转化为nonconst,这个只有const_cast才可以办得到 举例:

C++ Primer笔记6_STL之泛型算法

1.泛型算法: 大多数算法定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法 只读算法: 举例: find函数用于找出容器中一个特定的值,有三个参数 int val = 10;//val为我们需要查找的值 auto result = find(vec.begin(), vec.end(), val): cout << "The value "<< val << (result == vec.end() ? &qu

C++ Primer笔记13_运算符重载_总结

总结: 1.不能重载的运算符: . 和 .* 和 ?: 和 ::  和 sizeof 和 typeid 2.重载运算符有两种基本选择: 类的成员函数或者友元函数, 建议规则如下: 运算符 建议使用 所有一元运算符 成员函数 = () [] -> 必须是成员函数 += -= /= *= ^= &= != %= >>= <<= , 似乎带等号的都在这里了. 成员函数 所有其它二元运算符, 例如: –,+,*,/ 友元函数 3.前几篇中的实例,现在汇总Person类的程序:

C++ Primer笔记4_类的静态成员_IO库

1.类的静态成员 static成员变量与函数 static成员变量:必须在类外初始化:(const或引用类型变量必须在构造函数初始化列表里初始化) static成员函数: 不依赖于类,相当于类里的全局函数(可以由该类对象调用,也可以 类名::函数名()的形式调用) 不包含this指针,不能声明为const,声明为const表示不会改变对象,而static成员函数存在于任何对象之外. 相当于把访问范围限制在所在的类中!  注意:不能访问类中非static成员变量以及非static成员函数. 注意:

C++ Primer笔记3_默认实参_类初探_名字查找与类的作用域

1.默认函数实参 在C++中,可以为参数指定默认值,C语言是不支持默认参数的,Java也不支持! 默认参数的语法与使用: (1)在函数声明或定义时,直接对参数赋值.这就是默认参数: (2)在函数调用时,省略部分或全部参数.这时可以用默认参数来代替. 注意事项: (1)函数默认值只能赋值一次,或者是在声明中,或者是在定义中,都可以. (2)默认参数定义的顺序为自右到左.即如果一个参数设定了缺省值时,其右边的参数都要有缺省值.比如int f(int a, int b=1,int c=2,int d=

C++ Primer笔记7_STL之关联容器

关联容器 与顺序容器不同,关联容器的元素是按关键字来访问和保存的.而顺序容器中的元素是按他们在容器中的位置来顺序保存的. 关联容器最常见的是map.set.multimap.multiset map的元素以键-值[key-value]对的形式组织:键用作元素在map中的索引,而值则表示所存储和读取的数据. set仅包含一个键,并有效的支持关于某个键是否存在的查询. pair类型 首先介绍下pair,pair定义在utility头文件中,一个pair保存两个数据成员,类似容器,pair是一个用来生

C++ Primer 笔记 第三章

C++ Primer 第三章 标准库类型 3.1using声明 例: using namespace atd; using std::cin; 3.2string类型 初始化方式 string s1 默认构造函数,s1为空串 string s2(s1) 将s2初始化为s1的一个副本 string s3(“value”) 将s3初始化为一个字符串的副本 string s4(n, 'c') 将s4初始化为字符'c'的n个副本 getline读取整行文本 getline接受两个参数:一个是输入流对象和