左值引用

  • 左值引用是一种绑定,在使用时必须进行初始化
  • 引用并非一种对象,而只是为一个已经存在的变量所起的另一种名字(我们把具有存储空间的叫做对象)。因此引用无法再绑定到其他对象。
  • 引用类型的初始值必须是一个对象。不应该是一个值。除了常量引用。

move

std::move 本质上就是一个类型转换器,它在编译期将一个左值(Lvalue)强制转换为一个右值引用(Rvalue Reference)。当我们遇到了一个无法进行拷贝的类的时候,例如一个unique_ptr之类的,可以通过move在形式上将其移动进入函数内,但是实际上move仅仅是将其转换成了一个右值,通过类内部的 MyString(MyString&& other); 移动构造函数传参而已

指针

  • 指针本身也是一个对象。与引用类似,也实现了对其他对象的间接访问。
  • 指针一定要初始化
  • 指针类型需要和它所指向的对象严格匹配。但有两种例外:指向常量的指针允许指向一个非常量对象。
  • 指针的值应该为以下四种状态之一
    • 指向一个对象
    • 指向紧邻对象的下一个位置
    • 空指针,即没有指向任何对象
    • 无效指针,即除上述情况的其他值。访问无效指针将发生错误。

某些符号具有多重含义

1
2
3
4
5
6
7
8
{
int i = 42;
int &r = i; //紧随类型名出现,因此是声明的一部分r是一个引用
int *p; //紧随类型名出现,因此是声明的一部分,p是一个指针
p = &i; //&出现在表达式中,是一个取址
*p = i; //*出现在表达式中,是一个解引用
int &r2 = *p; //&是声明的一部分,*是解引用
}

生成空指针的方法

1
2
3
4
5
{
int p1 = nullptr;//int*p1 = 0;可以转换成任意其它类型的指针
int *p2 = 0;
int *p3 = NULL;
}

void* 指针是一种特殊的指针类型,可以存放任意对象的地址。

如何检查指针是否指向了一个合法的对象呢?

  1. 检查是否为NULL
1
2
3
if (p != NULL) {
// p 可能是一个有效指针(但不保证)
}
  1. 确保指针指向的是已分配的内存
1
2
3
4
5
p = malloc(sizeof(int));
p = new int;
if (p) {
// new 在分配失败时通常会抛出异常,除非使用 `nothrow`
}
  1. 检查指针是否为悬空指针
1
2
3
4
5
6
7
8
9
10
int *p = (int*)malloc(sizeof(int));
free(p); // p 现在是悬空指针
if (p) {
printf("p 不是 NULL,但仍然是无效的!\n");
}
//处理方法
free(p);
p = NULL;
//智能指针
std::unique_ptr<int> p = std::make_unique<int>(10); // 自动管理内存
1
2
3
4
5
6
7
8
9
{
const int i = 42;
auto j = i;
const auto &k = i;
auto *p = &i;
const auto j2 = i,&k2 = i;
cout<<j<<" "<<k<<" "<<*p<<" "<<j2<<" "<<k2;
//42 42 42 42 42
}

auto与decltype指示符

  • auto是自动配置声明变量的类型
  • auto一般会忽略掉顶层const,而底层const会被保留

string

  • 注意在字符串相加时,必须确保“+”两侧至少有一个string类型。,不能直接使用字面值相加。
  • string中包含了相当多的库函数,使用时可以问问gpt

迭代器

  • 容器的访问有两种方式
1
2
3
4
5
6
7
8
9
10
//下标访问,这里有一行比较巧妙地代码
vector<unsigned> scores(10,0);
unsigned grade;
while (cin>>grade)
{
if(grade<=100)
{
++scores[grade/10];
}
}
  • 但是需要注意的是,我们无法通过下标来实现添加元素。,应该实验push_back;
  • 另外值得注意的是,通过下标访问容器中不存在地元素会导致严重的错误,即缓冲区溢出。
  • 另外一种访问方式是迭代器方法
1
2
3
4
5
{
string s("my name");
auto it = s.begin();
*it = toupper(*it);
}
  • 在这种方式下要注意迭代器与指针的相似性与差异性

数组

  • 数组是一种类似vector的数据结构,但是数组的大小不变,不能够随时向数组内添加元素。
  • 在数组初始化的时候要注意字符数组的特殊,字符串字面值的结尾处还有一个空白字符。
  • 对于复杂数组的理解(类型修饰符从右向左依次绑定)
1
2
3
4
int *ptrs[10];//10个整型变量数组指针
int (*Parray)[10] = &arr;//指向十个整型变量的数组的指针
int (&arrRef)[10] = arr;//引用十个整型变量数组
int *(&arry)[10] = ptrs;//arry是数组的引用,该数组含有10个指针
  • 在使用数组时编译器一般会自动将其替换成为一个指向数组收地址的指针。

运算符

  • 重载运算符不能改变运算对象的个数,运算符的优先级,结合律
  • 当一个对象被用作右值时,用的时对象的值(内容)。当一的对象被用作左值时,用的是对象的身份(在内存中的位置)。
  • 对于逻辑与和逻辑或而言,都是先求左侧对象的值再求右侧对象的值,当且仅当左侧对象无法确定表达式的结果的时候才会继续计算右侧对象
1
2
3
4
int i = 0, j = 0;
j = i++; //j = 0,i = 1
j = ++i; //j = 2;i = 2
cout<<*p++<<endl;//输出当前值并指针后移一个单位
  • static_cast(name); //类型强转
  • const_cast(name); //常用于去掉变量的const属性
  • 但是强制类型转化干扰了正常的类型检查,所以应该减少使用。

C++11版本的for循环语句

1
2
3
4
5
for(declaration:expression)
//expression所表达的必须是一个序列。例如一个花括号括起来的初始值列表、数组、容器
//declaretion需要是一个能转换成该变量的类型。最好使用auto来声明。

statement;

泛型算法

  • 标准库定义了一组泛型算法实现了一些经典算法的公共接口,如排序和搜索,他们可以用于不同类型的元素和多种容器类型。
  • 注意泛型算法并不会改变容器本身的大小,并不能执行容器操作。
  • 常见函数
    1. find()
    2. accumulate()
    3. sort()
    4. unique()函数可以将重复元素放在末端,并返回一个指向最后一个不重复元素之后的位置。再通过erase函数实现元素的删除。
  • lamda表达式又称为匿名函数,一般的表达形式为
    • [capture list](parameter list) ->return type {function body}

其中capture list是一个局部变量列表

我们可以忽略列表和返回类型,但是必须永远包含捕获列表和函数体。

  • 迭代器的类型也有很多,包括输出迭代器,输入迭代器,前向迭代器,双向迭代器,随机访问迭代器。

关联容器

  • 关联容器并不支持顺序容器位置相关的操作,也不接受构造函数或者插入操作。
  • map容器可以用于键值-值的算法。set容器可以用于查找算法。
  • 对于multimap,同样一个键值可以对应不同的值,对于multiset,容器内可以存放重复的值
  • pair标准库类型,可以用于生成一个键值对,并可以为这个键值对命名

动态内存(堆)

  • 全局对象再程序启动时分配,程序结束时销毁;局部对象第一次使用前分配,函数结束时销毁,static程序启动时在静态存储区分配内存,程序结束时释放内存,析构函数会在程序退出时调用(如果有)。const如果是基础类型(如int、float),且编译器能够确定值,则通常优化为编译期常量,直接替换为字面值,不分配实际内存。如果编译器无法确定值(如引用外部变量),会分配在栈上。
    如果分配在栈上,则函数调用结束时销毁
    如果被优化为字面值,不存在分配和销毁的过程。
  • 而对于动态变量我们可以指定他的生存周期,也即我们需要显式的销毁这个对象。
  • 新标准库提供了两种新的智能指针,并且都在memory头文件中。

shared_ptr 允许多个指针指向同一个对象

unique_ptr 单独指针指向对象

weak_ptr 弱引用,指向shared_ptr的对象

  • 最安全的分配使用动态内存的方式时调用make_shared函数
1
shared_ptr<int> p3= make_shared<int> 42;
  • 当指向这个对象的最后一个shared_ptr被销毁的时候,sahred_ptr类会自动销毁这个对象
  • 传递给delete的指针必须要指向动态分布的内厝或者一个空指针,要注意,编译器无法分辨指针指向的是静态还是动态分配的一个对象。
  • const指针指向的对象同样可以被释放
  • 当我们delete一个指针后指针变为无效了,但是可能指针仍保存着已经被释放了的动态内存的地址,这儿时候我们应该将其赋值为NULL
  • 智能指针需要遵守的规范
    • 不使用相同的内置指针初始化多个智能指针
    • 不delete get()返回的指针
    • 不使用get()初始化或者reset()另一个智能指针
    • 如果使用了get返回的指针,那么最后一个智能指针销毁的时候,对应的指针会无效
    • 使用的指针管理的资源不是new分配的内存,那么应该传递给他一个delete
  • weak_ptr是一种不控制所指向对象生存期的智能指针。他指向一个shared_ptr管理的对象,并且将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用次数
  • 要注意动态数组在新版本下的性能往往不如一个容器。
    并且我们所创建的动态数组往往是数组元素类型的一个指针

面向对象程序设计

  • 面向对象程序设计基于三个基本概念:数据抽象,继承,动态绑定。数据抽象即设计一个类来实现同类数据的存储,可以帮我我们将类的接口与实现相分离;继承可以帮助我们定义一个相似但是并不完全相同的新类;动态绑定是与继承相适应的一个函数形态,是一种多态的函数,通过传入不同的形参来实现绑定不同的派生类。

本站由 Edison.Chen 创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

undefined