- 博客(17)
- 收藏
- 关注
原创 Vector的实现
不能这样做的原因是这样会导致迭代器失效,_begin指向新空间后,新的_size,_capacity需要通过_size(),_capacity()返回的偏移量来确定位置,而此时_begin已经指向了新空间,_size,_capacity还指向旧空间,试图用两个毫不相关的指针相减显然是荒谬的。在c++标准库里面可以看到它的实现,和我们经常用的数组不同,库里面并不是使用我们熟知的指向首元素的指针+元素个数+容量大小,而是使用了三个指针,分别指向的是,首元素,最后一个元素末尾,容量结束末尾。
2024-08-09 23:49:46
232
原创 string类的模拟实现
接着来看查找,在字符串中查找子串或字符并返回它的下标,查找字符简单,直接挨个挨个去比较,相等就返回该处的下标,找不到则返回npos。类似地,string类里面的迭代器也非常简单,直接返回指向的字符,begin()就让指针指向第一个字符,end()就让指针指向最后一个有效字符的下一个字符,也就是说所有的end()都应该指向的是‘\0’。string作为存储字符串的基本类,根据库里面的描述,字符串是用于存储连续字符的容器,并且库里面已经有相关的函数及其使用,底层也并不复杂,现在来实现一些较为常用的函数。
2024-07-07 00:18:15
473
原创 C++模板
如果是同一个函数,他们call的地址应当是相同的,但这里 Swap(a, b)它call的是Swap<int>的地址,Swap(c,d)它call的是Swap<double>的地址。在类和对象中,类名代表类型,类模板的实例化需要在类模板的名字后面加<>,在将实例化的类型放入<>里面,类模板的名字不是真正的类,真正的类是那个实例化的结果。所以a和c是无法完成交换的,因为他们的类型都无法匹配。交换两个数很简单,可以直接在函数里面交换,但如果不只是int类型,假如要交换的是double类型甚至是自定义类型呢?
2024-02-24 17:32:13
241
原创 new/delete实现的原理
于是大胆猜测,malloc/free可能被封装到某个中间操作符里面,在这里对malloc开辟空间失败返回空指针的方式进行优化,申请空间的步骤完成,然后在调用析构/构造函数,再将这个中间操作符封装到new/delete里面,这样才是C++面向类和对象的编程。一定情况下,delete[],delete,free都可以使用,但这只是少数情况,内存的申请和释放匹配使用不仅可读性更好,也更安全,保险起见,建议申请释放匹配使用。可以明确的说,即使是用free来释放,这里也不会有任何的问题。
2024-02-23 11:17:18
538
1
原创 动态内存管理
对于动态内存的管理,可以使用malloc和free申请和释放空间, 并且对于内置类型它是有能力初始化的,那如果是自定义类型该怎么办,一般成员变量是私有,在外面不能轻易访问,这种方式仅仅只能做到开辟空间而不能初始化。并且,new开辟空间明显会比malloc方便许多,malloc每次开辟空间都要算大小,还要在前面强制类型转换一下,new不需要这些繁琐的步骤,如果是多个对象,直接在[]里面指定对象个数,类型也会自动识别。当new的初始化是不完全初始化,比如这里的int类型,后面的数据都会初始化为0。
2024-02-21 12:30:19
605
原创 数据的存储方式
其实这里的nums和ch1并没有直接指向1和'h'的地址,直接用数组+方括号初始化,中间会先做一次拷贝,在栈区创建一个临时数组后,将常量区上的内容拷贝到这个临时数组里面,最后让nums和ch1指向这个数组,所以nums和ch1一定存在栈区上。当我们向内存主动申请空间的时候,动态开辟的内存无疑是在堆这个区域内申请的,与栈区不同,堆里面的空间不会自动清理,需要我们主动释放,否则将会导致内存泄漏。栈区地址的存储规则是,向下增长,所以这里可以看到,先定义的a变量的地址比后定义的b的地址更大。
2024-02-20 17:16:49
1211
1
原创 friend友元
这既给我们带来了方便,也存在一些弊端:它增加了耦合度,毕竟每一个使用友元的地方都有可能改变原来的私有成员,频繁的使用只会更加破坏封装,所以 友元不宜多用。在类的内部可以随意访问私有成员,类的外部不能随意访问私有成员,但总会有时候我们想要从类外面访问私有成员,每次专门为得到这个成员变量而写一个函数过于繁琐,直接用友元函数会更加方便。另外,在一个类中定义了内部类以后,计算它的大小时,不会计算内部类,因为内部类的存储其实并没有存在外部类中。B可以访问A的成员,但A不能访问B的成员。
2024-02-13 21:31:26
293
原创 隐式类型转换
d发生隐式类型转换后中间产生了一个临时变量,这个变量具有常属性,而这个常属性的变量试图赋给一个随时想改变的变量,那不就很扯淡了。再者,根据权限的角度也能说明这个问题,权限不能放大,很明显,这里就是一个权限放大。这又为什么,为什么提示说int&类型非常量限定,难道把c变量变成常量属性,即在前面用const修饰,就能编译通过吗。不难看出,由于类型不同,a,d在进行比较的时候,必定产生了隐式类型转换,当类型一致时,方可完成比较。可是,a和d在比较时发生的隐式类型转换,会不会影响a,d本身?
2024-02-05 11:10:48
285
1
原创 运算符重载的运用
对于自定义类型可以自定义它的运算规则,我们来看看以下一些问题1.哪个操作符不能被重载 ( )C++规定,不能被重载的运算符只有5个, 点号. 三目运算?: 作用域访 问符:: 运算符sizeof 以及.*所以这题正确答案是C。2.在重载一个运算符为成员函数时,其参数表中没有任何参数,这说明该运算符是 ( )没有如何参数,其实并不是真的没有传参数,而是只隐式的传了一个this指针,所以只有一个参数。至于如何判断前缀还是后缀,就++和--而言,前置无需传参,而后置需要传int。
2024-02-04 20:11:13
1310
1
原创 类和对象的初始化方式
假设类里面有一个const修饰的变量需要初始化,凭借构造函数仍然不行,编译器的报错也不难理解:构造函数的本质是赋初值,既然是赋值,那它凭什么可以随意修改const修饰的变量呢。之所以会输出这样的结果,是因为成员变量初始化的顺序其实与其在初始化列表中的先后次序无关,而应该取决于其在初始化列表中的初始化顺序。初始化列表完成初始化,但好像不用初始化列表,仅依靠构造函数似乎也可以做到这样的事,那为什么要引进初始化列表?_b比_a更先声明,理应是_b优先初始化,_b拿一个未初始化的_a来初始化,也就会是随机值。
2024-02-03 21:41:09
280
1
原创 static修饰规则
直接在私有成员里面创建一个count变量来统计肯定是不行的,每次创建一次count就++一下,但这样只能统计创建的那个类的一次,相当于每个类里面的count都是1,显然无法统计创建的次数。反之,非静态成员函数却可以调用类的静态成员函数,非静态成员函数在调用时会默认传this指针,故非静态成员函数能够找到类里面的成员函数,进而调用函数。静态成员函数不能调用非静态成员函数,因为静态成员函数没有this指针,它无法找到类里面的非静态成员函数。和C的语法相似,在内存中的存储同样也是存在静态区,并且具有全局属性。
2024-02-02 20:58:08
702
原创 const修饰规则
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。将d2传到Print函数里面时,它会用一个指向d2的指针来接收,而且这是默认非const的指针,所以由const保护的类传给非const的函数就是一个典型的权限放大问题。我们很容易发现,不管什么样的对象,都可以调用const修饰的成员函数,那是不是以后直接将所有的成员函数写成const修饰的?权限既可以缩小,也可以平移,就是不能放大。
2024-02-01 20:36:39
262
1
原创 运算符重载
和内置类型的加法类似,我们不希望改变原来的值,而是返回一个加了之后的值,所以这里得创建临时的Date类,最后返回这个临时变量,而不是返回this。只要不是大于,那肯定就是小于等于,我们判断小于等于就可以借助上面写过的大于比较,如果是大于就一定不是小于等于,返回假,否则就一定是真。祖师爷进行区分的方式是,将后置++或--里面的参数加上int,这样参数类型和前置++或--操作的不同,使其构成重载。这里返回的是this指针指向的内容,传进来是this指向的内容,返回的也是它,故这里应该用引用返回。
2024-01-30 13:03:23
1200
1
原创 类和对象之拷贝构造
假如我们用一个自定义类型来接收参数,类型确实是匹配了,但遗憾的是,代码编译到这就不会往下走了,因为在你调用这个函数之前必须传参,而传参数必定又会调用新的拷贝构造....如此循环,就会发生无穷递归。有时我们只有值拷贝,不涉及到动态空间申请时,用浅拷贝就够了,毕竟编译器能够自动帮我们生成,但凡涉及到动态内存申请,我们应该使用深拷贝,否则就会出现如上错误。是的,这样的情况下我们的确不需要写拷贝构造,编译器自动生成的拷贝构造也能满足我们的需求,因为这里面只有int型。如此,便完成了深拷贝。
2024-01-26 10:53:46
422
1
原创 类和对象之构造函数与析构函数
析构函数像是构造函数的亲兄弟,如果说构造函数是头,那么析构函数就是尾巴,析构函数的使用只需在构造函数前面加上“~”符号,并在类的生命周期即将结束时调用此函数,可以说析构函数就是为清理空间而存在。每次都要初始化和清理较为繁琐,于是乎,C++提出了构造函数和析构函数,构造函数可以帮助我们完成初始化,而析构函数可以帮助我们进行空间清理。在C语言中,我们在写一些项目时难免会要进行各种初始化和空间清理,但是有时候也可能会忘记初始化或空间清理,就会导致一些问题。构造函数放在类里面,函数名与类名相同,无返回值。
2024-01-25 11:12:06
337
原创 初识类和对象
不难看出,将变量及相关的函数一起放入到一个类中更加有利于形成一个紧密的联系,这种将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互的方式叫做封装。假设函数也直接存储在每个类里面,由于每个对象中成员变量是不同的,但是调用同一份函数,当一 个类创建了多个对象时,每个对象中都会保存一份代码,相同的函数代码保存多次,必然会造成空间浪费。事实上在类中,函数所占的空间并不会存储到类里面,而是存到公共代码区,所以在计算类的大小时不会计算函数的大小。C++为什么要引入类?
2024-01-24 12:53:38
325
原创 C++入门
C代码可以在.cpp文件中运行,并且C和C++在语法上有许多相似的地方,C++对C的一些语法进行了改进,补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的。显然,此时运行编译器会报错,原因就是库里面已经有了定义的rand函数,而主函数里面的rand变量必然会引起冲突,C语言如果想让程序正常运行,只能改变主函数中的rand变量名。第一,在C中,命名冲突的问题非常明显,当有重名的变量或函数时,只能将其中一个名字更改。第二,C每次输出打印都需要传指定的类型,如:整形用%d,字符串用%s等。
2024-01-22 22:32:37
295
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人