C++Primer学习笔记(2)

 这篇笔记的内容是字符串、向量和数组。

字符串和向量都是抽象数据类型,而数组则相对更基础,个人认为算是介于抽象数据类型和基本内置类型之间吧。

一、命名空间的using声明

在之前(1)的笔记中给出过using跟include区别的链接。个人认为namespace的作用范围要比include大的。如果要进一步了解namespace参考:C++/C++11中命名空间(namespace)的使用_网络资源是无限的-CSDN博客

注意的是:

还有就是我们经常只使用using namespace std直接声明整个std,但是这样还是比较笼统。用类似using std::cin声明会更容易理解cin是std里面的,这个在项目较大的时候应该会有帮助。

关于namespce怎么用举两个例子:

1.namespace跟main函数在一个文件里的话

namespace B
{
    int x = 1;
}
using namespace B;//要放在定义的下面
int main()
{
    cout << x << endl ;
    return 0;
}

这个时候对里面的数据类型没太大要求,应该是这个时候x的作用域就是在该页面。

2.要跨文件的话(namespace跟main不在一个文件)

首先是要新建一个头文件和源文件的,假设为a.h跟a.cpp

//a.h文件
#pragma once
namespace A 
{
	extern int y;
    /*
变量只能为const或者static才可以调用,个人理解是不加修饰的话只能在局部调用.
如果直接加const或者static修饰的话可以不必在a.cpp文件赋值了,
      应该是这两个修饰符代表就是全局变量了,否则只能加extern关键字
*/
	double max(int size);

}
//a.cpp
int A::y = 11;
double A::max(int size) {
	return size;
}
//main.cpp
int main()
{
    A::y=11;//这时候的y可以更改,要是在a.h定义为const的话就不能改了
    cout << y << endl ;
    return 0;
}

二、标准库类型string

 string是官方封装的,效率肯定不低,至少比我们自己用基本数据类型随便封装的会高的多。这里主要记住一下要用string要导入的头文件和命名空间

1.定义与初始化string对象

关于直接初始化与拷贝初始化

 关于拷贝初始化与直接初始化即看看是否是等号直接赋值。

2.对string对象的操作

可理解为string有哪些函数和变量。

1)读写string对象

这个说明采用cin输入的话是无法给string赋值包含空格的字面值的,那咋样才能让string里面包含空格呢?

还是证明了cin输入string变量的时候是可以以空格作为分隔的。然后试试用while。

int main()
{
    string s1;
    while (cin >> s1) {
        cout << s1 << endl;
        if (s1 == "11")break;
    }
    return 0;
}

 直到遇到11的时候才会停止,并且这个是不断覆盖的,最后s1="11"。

 2)使用getline读取一整行

用这个办法就可以给字符串赋值包含空格的字符串字面值了,即这个是赋值一整行,而cin是一个词一个词赋值。

3)string的empty和size函数

 4)string::size_type类型

大致要知道size()返回的是一个无符号数。

5)string之间的比较

 记住两个比较规则。具体实现应该是字符串按字符一位一位比较Ascii码值。

6)字面值与string对象相加

两个原则:a.加的顺序,要按照从左到右的优先级,并且(字面值常量+string变量)与(string变量+字面值常量)最后都是string变量;

b.加号两边一定要有一边是string对象

举例一:

string s1="11";
string s = "1" + s1+"2"+"22"+"111";

这个是对的,因为“1”+s1是string变量,并且接下来从左到右依次叠加都是string变量。

 举例二:

string s1="11";
string s = "1" + "121"+s1+"2"+"22"+"111";

这个是错的,因为“1”+"121"加号两端没有string变量。

3.处理string对象中的字符

1)系统中的字符处理函数

 2)范围for语句遍历字符串每个字符

也可以把string看成一个字符数组,直接用下标进行访问。

三、标准库类型vector

vector 被称为容器,其是类模板。

 1.定义和初始化vector对象

1)默认初始化

 2)列表初始化

 

注:之前string中的初始化是从直接初始化与拷贝初始化的角度去介绍,这里则是从列表初始化与值初始化的角度去介绍 

3)值初始化

 4)关于列表初始化与值初始化

二者区别在于圆括号与花括号。

 2.vector对象添加元素

注意:vector对象值初始化确定元素数量后,还可以使用push_back继续添加元素。如下例子:

    vector<int> vec(2, 0);
    cout << vec.size() << endl;//输出2
    vec.push_back(2);
    cout << vec[2] << endl << vec.size() << endl;//输出2和3

3.vector的其他操作

 基本上其他操作都跟string类似,具体可参考string的内容。

关于vector的下标:

 四、迭代器介绍

使用typeid打印出来的vector<int>的迭代器类型 

 1.迭代器运算符

迭代器和指针的使用方法类似,基本上可以把它当成一个指针来用。

 1)迭代器类型

 2)begin与end运算符

即使用cbegin与cend可以使得迭代器为const。 

 3)迭代器失效

 2.迭代器运算

五、数组

数组也是容器。

1.定义和初始化内置数组

 即如果想动态控制维度,还是别用是数组了。用vector吧。

1)显式初始化数组元素

 需要注意的是自动初始化默认值。

 2)字符数组的特殊性

即字符数组的最后一位一定要是空字符。未声明维度时, 字符数组的长度为字符字面值个数+1(即自动补充的空字符)。如果声明了维度,那么字符中字符字面值个数只能为维度减一,最后一位要留来给空字符。

3)不允许拷贝和赋值

 一般来说其他语言则是可以的,这个也算是C++比较不人性化的地方。不过可以通过数组指针来拷贝。

4)复杂的数组声明

有指针数组,没有引用数组(着重点在数组元素);而有数组的指针也有数组的引用(着重点在数组)。 

关于&符和*符修饰的是数组还是元素还是很容易弄混的,需要背一下。

 2.数组元素的访问

其他的访问方式与vector类似。

注意:在使用范围for语句的话,关于参数传递要把它当成函数一样对待,它也是分为值传递、引用传递的。

	char a[3] = {'1','2'};
	for (auto f : a) { 
		f = 'b';
		cout << f;//依次输出bbb
	}
	for (auto f : a) {
		cout << f;//依次输出12空格
	}
	for (auto &f : a) { 
		f = 'c';
		cout << f;//依次输出ccc
	}
	for (auto f : a) {
		cout << f;//依次输出ccc
	}

所以如果要用范围for语句对数组赋值的话要记得使用引用传递

3.指针与数组

 即数组的名字等价于数组第一个元素的指针。

1)指针与迭代器

迭代器能干的活,指针也能干。指针也是迭代器。

2)标准库函数中的begin和end

注意最后一点,尾后指针无法用于解引用与递增操作(因为递增后可能会访问未规划的内存造成数组访问越界的现象,此类错误在C++编译器中不会提示),但是拿来递减后解引用还是可以的。

3)数组与指针的其他骚操作

两个指针相减,最后得出的类型是一种标准库类型。

 指针与下标

要注意一下,C++中数组的下标是很随意灵活的,同时也很容易翻车。

	char a[3] = {'1','2'};
	char *endp = end(a),*startp=begin(a);
	cout << *(endp+1) << endl << endp[-2] << endl<< a[-2] << endl;
	*(a+4)= 9;
	cout << a[3] << endl;

如图所示,这样搞C++都不会报错的,不过在超出数组范围内还要访问的话输出为空

这个举例也说明了数组跟指针是紧密联系的。

4.C风格字符串

引起 安全漏洞是比较可怕的事。

1)常见的C风格字符函数

 注意的是:定义的字符数组最后要加上空白字符,这样才方便让C风格函数调用,否则容易出错。

2)其他注意

 5.数组与标准库类型之间

 1)string与字符数组

要把string当成字符数组去用指针访问的话要使用c_str函数。

2)数组初始化vector对象

 vector无法初始化数组,但是数组可以初始化vector对象。

六、多维数组

可以把多维数组理解成数组嵌套。 

1.多维数组初始化

 这些初始化的规律只能是不断重复实践背下来了。

2.多维数组下标

3.范围for语句处理多维数组

 

关于多维数组在使用范围for语句时,前n-1层的循环要写成引用是有讲究的,否则这些参数将会被识别为指针,然后就会报错的。只有最后一层才能自由声明。(即参考一维数组,范围for语句中的参数的类型是int指针类型的,而前面的n-1层需要是数组不能是指针)这个感觉也没规律,只能硬记了

4.指针和多维数组

 关于数组的指针还是指针的数组需要多记一记,要不然容易弄混。

然后举个例子说明多维数组的多级指针思想

	int ia[2][3][4] = { 0 };
	int(*p)[3][4] = &ia[1];
    //在赋值时,ia默认等于&ia[0]
    //p是最底层的指针,是一个三级指针,要用三个*号才能将其彻底解引用
	int(*p1)[4] = p[0];
    //p1是一个二级指针,要用二个*号才能将其彻底解引用
	int(*p2) = p1[0];
    //p2是一个普通指针,用一个*号能将其解引用
	cout << *p2 << endl<< p1[0]<<endl;//最终打印出0还有ia[1][0][0]的地址

可以从多级指针的角度理解多维数组的指针问题,这一块的逻辑有点复杂。

1)使用auto或decltye

使用自动类型推断能降低工作量,更好简化指针的访问逻辑。根据auto推断出来的指针依然符合上一条的多级指针的举例。

 2)使用类型别名进行简化

感觉还是没有直接类型推断来的快。 

3)多维数组中的地址

为了验证一下多维数组中的元素是不是顺序存储的,

	int ia[3][3] = {1,2,3,4};
	int* q = &ia[0][0];
	for (int k = 0; k < 9; ++k)cout << *(q+k)<<"  "<<q+k<<"  ";
	cout << endl;
	for (auto& i : ia)
		for (auto& j : i)cout << j << "  " << &j<<"  ";

结果证明是顺序存储的。所以如果想贪快遍历整个多维数组的话用一个指针,一层循环就够了。

猜你喜欢

转载自blog.csdn.net/qq_42987967/article/details/121672422