c++小知识(2)
-
一、引用和指针的区别:
指针:从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。
引用:是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。
指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向 某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给 该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
如果操作符[]返回一个指针,那么后一个语句就得这样写:
但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款30)当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。
-
二、c++ const引用详解
(1)在实际的程序中,引用主要被用做函数的形式参数--通常将类对象传递给一个函数.引用必须初始化. 但是用对象的地址初始化引用是错误的,我们可以定义一个指针引用。
(2)一旦引用已经定义,它就不能再指向其他的对象.这就是为什么它要被初始化的原因。
(3) const引用可以用不同类型的对象初始化(只要能从一种类型转换到另一种类型即可),也可以是不可寻址的值,如文字常量。例如:
上面,同样的初始化对于非const引用是不合法的,将导致编译错误。原因有些微妙,需要适当做些解释。
引用在内部存放的是一个对象的地址,它是该对象的别名。对于不可寻址的值,如文字常量,以及不同类型的对象,编译器为了实现引用,必须生成一个临时对象,引用实际上指向该对象,但用户不能访问。
例如:
编译器将其转换为:
同理:上面代码
(4)不允许非const引用指向需要临时对象的对象或值,即,编译器产生临时变量的时候引用必须为const!!!!切记!!
(5)********对于const int *const & pi_ref = &iva; 具体的分析如下:*********
1.不允许非const引用指向需要临时对象的对象或值
2.地址值是不可寻址的值
3.于是,用const对象的地址来初始化一个指向指针的引用
const引用的语义到底是什么?最后,我们可能仍然不明白const引用的这个const的语义是什么
const引用表示,试图通过此引用去(间接)改变其引用的对象的值时,编译器会报错!
这并意味着,此引用所引用的对象也因此变成const类型了。我们仍然可以改变其指向对象的值,只是不通过引用 下面是一个简单的例子:
其中第10行,如果我们通过ir来改变val的值,编译时会出错。但是我们仍然可以通过val直接改变其值(第9行)
总结:const引用只是表明,保证不会通过此引用间接的改变被引用的对象!
另外,const既可以放到类型前又可以放到类型后面,放类型后比较容易理解:
三、值传递、地址传递、指针传递的区别
1、值传递:
值传递是最常见的参数传递方式。当我们将一个值传递给函数时,函数会创建该值的副本,并在函数内部使用这个副本。这意味着函数对该值的修改不会影响到原始值。
优点:
简单直观,易于理解和使用。
函数内部对参数的修改不会影响到原始值,确保了数据的安全性。
缺点:
需要额外的内存来存储参数的副本,可能会导致内存消耗过大。
对于大型对象的传递,值传递会导致性能下降。
适用场景:
对于简单的数据类型,如整数、字符等,值传递是最合适的选择。
当函数不需要修改原始值,只是使用该值时,值传递也是一个不错的选择。
值传递例子:
在这个例子中,我们将一个整数值传递给changeValue
函数。在函数内部,我们将参数num
的值修改为5。然而,当我们在main
函数中打印num
的值时,发现它仍然是10。这是因为在值传递中,函数会创建参数的副本,而不是直接修改原始值。
2、指针传递
指针传递是通过将参数的内存地址传递给函数来实现的。函数可以通过指针来直接访问和修改原始值。
优点:
可以直接修改原始值,避免了创建副本的开销。
对于大型对象的传递,指针传递比值传递更高效。
缺点:
需要手动管理内存,包括分配和释放内存空间。
指针可能为空或者指向无效的内存地址,需要进行有效性检查。
适用场景:
当函数需要修改原始值时,指针传递是一个不错的选择。
对于需要传递大型对象的情况,指针传递可以提高性能。
例子2:指针传递
在这个例子中,我们将指向整数的指针传递给changeValue
函数。在函数内部,我们通过解引用指针来修改原始值。当我们在main
函数中打印num
的值时,发现它已经被修改为5。这是因为指针传递允许函数直接访问和修改原始值。
3、引用传递
引用传递是将参数的引用传递给函数。函数可以通过引用直接访问和修改原始值,而无需创建副本。
优点:
可以直接修改原始值,避免了创建副本的开销。
使用起来更加简洁,不需要手动管理内存。
缺点:
无法传递空值,需要保证引用的有效性。
对于初学者来说,理解引用的概念可能有一定难度。
适用场景:
当函数需要修改原始值时,引用传递是最常用的方式。
对于大型对象的传递,引用传递可以提高性能。
例子3:引用传递
在这个例子中,我们将整数的引用传递给changeValue
函数。在函数内部,我们可以直接修改原始值,而无需使用指针或副本。当我们在main
函数中打印num
的值时,发现它已经被修改为5。这是因为引用传递允许函数直接访问和修改原始值,使得代码更加简洁和易读。
四、new & delete/堆与栈对比
C++是一门面向对象的高级语言,在我们编写代码中,常常离不开对对象的创建和清理对象资源。而兼容过来的malloc和free并不能很好的满足我们的需求,从而C++将malloc和free封装起来并起了新的名字new和delete,这两个关键字的作用不仅比malloc和free的功能强大,用起来也非常的方便,下面我们来看看new和delete的用法
new 和 delete对内置类型的操作
new和delete都是运算符,不是库函数,不需要单独添加头文件
格式:
new
1、类型指针 指针变量名 = new 类型
2、类型指针 指针变量名 = new 类型(初始值)
3、类型指针 指针变量名 = new 类型[元素个数]
delete
1、delete 指针变量名
2、delete[] 指针变量名
五、C++ STL set容器常用用法
所有元素都会在插入时自动被排序,set容器默认排序规则为从小到大
本质:
- set/multiset属于关联式容器,底层结构是用二叉树实现。
set和multiset区别:
- set 不允许容器中有重复的元素
- multiset允许容器中有重复的元素
1.set容器的常用操作
使用时注意包含头文件<set> std::set and std::multiset associative containers
s.begin() 返回set容器的第一个元素
s.end() 返回set容器的最后一个元素
s.clear() 删除set容器中的所有的元素
s.empty() 判断set容器是否为空
s.insert() 插入一个元素
s.erase() 删除一个元素
s.size() 返回当前set容器中的元素个数
set模板原型://Key为元素(键值)类型
template <class Key, class Compare=less<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) >
2.set容器的创建
3.set容器的增删改查
(1)插入:
【注】:insert()函数可以插入单值,也可以插入数组。例如:s.insert(a,a+4)
(2)删除
【注】:删除set容器中的元素可通过四种方式:直接使用erase(元素值)来删除某个元素;使用erase(it迭代器)删除迭代器指向的位置元素;使用迭代器指向删除区间,实现删除某段元素,erase(ita,itb)删除[ita,itb)的元素,注意itb指向的元素不会被删除。
(3)修改:不能直接修改set容器内的内容,所以只能通过删除后再插入才可。
(4)查找:s.find(),查找一个元素,如果不存在,则返回s.end()的值
(5)set的其他常用操作
【注】:查看容器里的值得加*;s.equal_range()的两个值分别通过.first和.second访问。
(6)判断元素是否在set中 & 判断set是否为空
【注】:s.empty()返回值是bool类型;s.count()是计算元素在容器中出现的次数,因为set容器不允许重复,所以count的返回值只能是0或者1,注意区分find()函数。
(7)自定义比较函数
(8)set和multiset区别
掌握set和multiset的区别
- set不可以插入重复数据,而multiset可以
- set插入数据的同时会返回插入结果,表示插入是否成功
- multiset不会检测数据,因此可以插入重复数据
(9)pair对组创建
成对出现的数据,利用对组可以返回两个数据
(10)改变排序规则
放置自定义数据类型
六、数组的一些注意事项
一维数组定义的3种方式:
(1)数据类型 数组名[ 数组长度 ];
【注】:定义数组时,若未给定数组元素的初始值,则必须指定初始数组长度,否则提示错误:“不允许使用不完整的类型”。比如声明int a[] = {};空数组是不允许的,在这种情况下,你可能需要一个足够大的数组来存储输入的整数。你可以使用动态分配的方式,或者使用 std::vector
代替数组。
(2)数据类型 数组名[ 数组长度 ] = { 值1,值2 ...};
【注】:数组初始化时,若大括号{ }内的元素个数小于定义的数组长度,则剩余数组元素默认使用 0 填充。
(3)数据类型 数组名[ ] = { 值1,值2 ...};
【注】:定义数组元素初始值时,数组可以不指定初始数组长度。
七、map容器详解
(1)map构造和赋值
map常见的构造函数:map最基本的构造函数 map<关键字,键值> mapint
(2)map大小和交换
(3)map插入和删除
(4)查找
八、vector容器的两种访问方式:数组和迭代器
(1)数组访问的情况:
1、简单的遍历操作:如果只需要对容器进行简单的遍历操作,而不需要在遍历过程中修改容器内容,则可以使用数组方式访问。这样做通常更简洁和直观。2、只需读取容器内容:如果只需要读取容器中的元素而不需要修改,也可以使用数组方式访问,因为数组访问更直接,性能更好。
3、需要知道容器大小:如果需要知道容器的大小,并且不需要进行插入、删除等操作,也可以使用数组方式访问,并直接使用容器的 size()
方法获取容器大小
(2)迭代器访问的情况:
1、需要修改容器内容:如果需要在遍历过程中对容器的内容进行修改,如插入、删除、更新等操作,则必须使用迭代器访问,因为数组方式无法直接支持这些操作。
2、需要在容器中进行搜索或定位:如果需要在容器中进行搜索或定位操作,并且需要灵活地移动迭代器以满足搜索条件,则可以使用迭代器访问,因为迭代器提供了灵活的定位和移动功能。
3、需要实现泛型算法或函数:如果需要编写泛型的算法或函数,适用于各种类型的容器,而不仅仅局限于 vector
,则可以使用迭代器作为参数,以支持各种类型的容器。
九、C++ STL容器 —— string 用法详解
(1)构造函数
string str, str1;//定义空 string 容器
string str2("abcde");//1 //定义新容器, 将 “abcde” 作为初值
string str3 = "abcde"; //同上
string str4{ "abcde" };//2 //同上
string str5 = { "abcde" }; //同上
string str6{ 'a','b','c','d','e' }; //同上
string str7(6, 's'); //定义新容器, 将 6 个 ‘s’ 作为初值
string str8(str); //定义新容器 str8, 拷贝 str 所有的元素
string str9 = str; //同上
string str10(str, 2); //定义新容器 str10, 拷贝 str[2]~str[n-1]
string str11(str, 2, 3); // 定义新容器 str11, 拷贝 str[2]~str[2+3-1]
string str12(str.begin(), str.end()); //定义新容器 str12, 拷贝区间内元素
【注】:1 和 2 的区别:括号不同
小括号是复制的作用, 是将"abcde"复制给 str
大括号是赋值的作用, 是给 str 赋值 “abc”
(2)迭代器
str.begin(); 返回迭代器, 指向第一元素
str.end(); 返回迭代器, 指向最末元素的下一个位置
str.cbegin(); 返回迭代器, 指向第一元素, 类型为const
str.rbegin(); 返回反向迭代器, 指向反向迭代的第一元素
str.rend(); 返回反向迭代器, 指向反向迭代的最末元素的下一个位置
str.crbegin(); 返回反向迭代器, 指向反向迭代的第一元素, 类型为const
【注】:
begin和rbegin的区别:
str.begin()返回迭代器,指向容器内的第一元素
str.rbegin()返回逆序迭代器,指向容器内的最后一个元素
begin和cbegin的区别:
可以通过str.begin()修改容器内元素的值
不能通过str.cbegin()修改容器内元素的值
下标/at:支持下标 [] 和 at 函数随机访问容器内字符
str[id]; 返回下标为 id 的字符, 不检查是否越界
str.at(id); 返回下标为 id 的字符, 如果越界抛出异常
(3)assign (赋值函数)
(4)连接符
(5)swap (交换函数)
(6)常用函数
(7)长度/空间/容量相关函数
(8)添加元素
insert (插入函数):
【注】:insert()函数是在位置之前插入
append (连接函数)
【注】:append()函数是在位置之后插入。
append()
主要用于添加字符串或者子串到字符串的末尾,可以一次性添加多个字符。push_back()
主要用于添加单个字符到字符串的末尾。
(9)删除元素
erase (删除函数)
(10)更改数据
replace (替换函数) 返回 str
(11)copy (复制函数)
返回复制的字符数量
大小写转换
(12)查找数据
find系列 (查找函数)
能找到返回下标,否则返回str.npos
包括六种:
find / rfind
find_first_of / find_first_not_of
find_last_of / find_last_not_of
其他五个函数的参数用法参考 find 函数
【注】:find 和 find_first_of 的区别:
find 需要子串和父串全部匹配
find_first_of 只需匹配一个字符就可以
例: str=“abcabcabc”, str1=“cab”
str.find(str1) 返回 2, 因为需要完全匹配
str.find_first_of(str1) 返回 0, 因为 str1 中的字符 ‘a’ 在 str 中第一次出现的下标是 0
(13)substr (子串查找)