C++学习相关
interator
interator是c++标准库中的迭代器 ,基本使用方式为:
运行下面代码
#include <list> #include <iostream> using namespace std; int main(void) { int a[]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; list<int> name(a,a+10); list<int>::iterator it; for (it = name.begin(); it != name.end(); it++) { cout << *it << endl; } return 0; }
vector
以上使用的是数组, 我们也可以使用vector,然后使用iterator遍历:
运行下面代码
#include <vector> #include <iostream> using namespace std; int main() { vector<int> ivec; ivec.push_back(1); ivec.push_back(2); ivec.push_back(3); ivec.push_back(4); for(vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); ++iter) { cout << *iter << endl; } return 0; }
在类中调用全局变量的方法;
x ==>> 1
运行下面代码
#include <stdio.h> int x = 10; class Test { public: int x = 1; void show(){ printf("int is %d", x); }; }; int main() { Test t; t.show(); return 0; };
::x ==> 10
运行下面代码
#include <stdio.h> int x = 10; class Test { public: int x = 1; void show(){ printf("int is %d", ::x); }; }; int main() { Test t; t.show(); return 0; };
c++头文件 保护
C++中,一般我们会为了防止头文件被包含多次,都会在每个头文件中写与如下类似的代码:
运行下面代码
#ifndef A_H #define A_H ......//头文件内容 #endif
并得到执行后,“A_H”就被定义了,并且头文件的内容会进入编译,直到遇见“#endif”。
而一旦该头文件即将被错误地包含第二次时,与语句“#ifndef A_H”不符,因为第一次你已经定义过“A_H”了,所以“#ifndef A_H”之后的语句不会进入编译了,直到遇见“#endif”。 若头文件被包含一次以上,编译时都会报错。C++头文件保护符的目的就是避免这类错误
内存申请和返还内存
堆栈上的数据是编译的时候已经计算出来的, 所以堆栈中的数据只要超出了当前的上下文, 系统会自动回收内存;
堆上的数据是是软件在运行的时候生成的, 理论上可以向MM无限索取内存, MM不会管理内存, 它只会给和回收, 这个给了开发者很大的自由, 但是也是一个大坑, 用完一定要记得还给人家, 要么软件会越用越卡;
通过malloc申请内存的案例:
运行下面代码
#include <stdlib.h> #include <stdio.h> int x = 10; class Test { public: int* c; int x = 1; void show(){ printf("int is %d", ::x); }; void getMalloc() { c = (int *)malloc(1024 * 1024 * 1024); } void freeMall() { free(c); } }; int main() { Test t; t.getMalloc(); t.show(); t.freeMall(); return 0; };
1024*1024*1024 = 1G;
下面这张图是任务管理器的视图, 当前的Project2.exe向内存中申请了1个G的内存, 内存跳好高;
默认构造函数:
假如我们不写默认构造函数, 就不能定义构造函数数组, 编译器会跑错, 要养成写默认构造函数的习惯;
运行下面代码
#include <stdio.h> int x = 10; class Test { public: Test(int i) { }; int* c; int x = 1; void show(){ printf("int is %d", ::x); }; }; int main() { Test t[4]; return 0; };
这个才是正确的打开打开方式么么哒》》, 要写默认构造函数, JS中是必须写的好吗..
运行下面代码
#include <stdio.h> int x = 10; class Test { public: Test(){} Test(int i) { }; int* c; int x = 1; void show(){ printf("int is %d", ::x); }; }; int main() { Test t[4]; return 0; };
构造函数和析构的初始化顺序
构造函数实例化的时候, 会先实例构造函数内部的构造函数, 然后调用构造函数初始化,
析构的时候的顺序和构造函数初始化是相反的;
演示new 和 delete的使用;
运行下面代码
#include <stdio.h> int main() { //one int * p = new int(); //set value; int * q = new int(123); int * w = new int[100]; delete p; delete q; delete []w; return 0; };
使用 new struct:
运行下面代码
#include <string.h> struct Student{ char name[100]; int age; }; int main() { Student *p = new Student; p->age = 10; strcpy_s(p->name, "nono"); delete p; };
new constructor, 构造函数:
运行下面代码
#include <string.h> class St { public: char name[12]; int age; St(char _name[], int _age) { strcpy_s(name, _name); age = _age; } }; int main() { char name[] = "nono"; St *p = new St(name,12); delete p; };
malloc 和 new 最主要的区别
malloc 和 new 这两个关键字最主要的区别是, new 会初始化内存同时给内存设置值, 而malloc只负责分配内存;
private关键字是指, 只能内部调用, (其实子类是继承父类的private相关值或者函数,只是编译器限制了开发者的访问);
procted关键字是指, 可以内部调用, 可以被继承, 无法外部调用 ,
public关键字, 没有限制,可以继承, 可以外部访问;
子类中可以通过::父类方法调用父类的方法:
运行下面代码
#include <string.h> #include <stdio.h> class Parent { public : int p = 16; Parent() { } void print() { printf("printf parent"); } }; class Child : Parent { public: Child (){ } void print() { printf("printf child"); Parent::print(); } }; int main() { Child child; child.print(); };
virtual关键字的作用:
virtual是会自动继承的, 加了vitrual关键字的方法, 实例会调用实例实际类型的成员方法, 不会调用父类的成员方法, 案例:
运行下面代码
#include <string.h> #include <stdio.h> class Parent { public : int p = 16; Parent() { } void print() { printf("printf parent"); } }; class Child : Parent { public: Child (){ } void print() { printf("printf child"); } }; int main() { Child child; Parent* p = &child; p.printf(); //打印父类
};
如果给方法加了virtual关键字的话:
运行下面代码
#include <string.h> #include <stdio.h> class Parent { public : int p = 16; Parent() { } virtual void print() { printf("printf parent"); } }; class Child : Parent { public: Child (){ } virtual void print() { printf("printf child"); } }; int main() { Child child; Parent* p = &child; p.printf(); //打印子类 };
所以添加virtual是有用的,避免调用错误的函数, 一直调用实例对应的成员函数;
如果父类有virtual关键字方法的话, 那么子类就必须实现这个virtual方法, 否者使用父类指针的时候如何调用子类的virtual方法, 所以有virtual关键字都要实现, 这个是必要的。
在函数形参表后面写上 “= 0” 以指定纯虚函数, 纯虚函数是用来实现一套规范的, 当你继承这个类的时候必须实现这些接口;
调用复制(拷贝)构造函数的三种方法
运行下面代码
#include <string.h> #include <stdio.h> class Parent { public : int p = 16; Parent() { } Parent(const Parent& obj) { this->p = obj.p; //在函数内部的this是指向 实例的指针; } }; void Test(Parent p) { } void Test1(Parent &p) { } int main() { Parent parent; //parent为堆栈上的地址; Parent p1 = Parent(parent); //p1也是堆栈上的一个地址; Parent* p2 = new Parent(parent); //通过new出来的p2才是一个指针,这个指针指向p2 Test(parent);//把parent给一个局部方法, 此时是复制; Test1(parent);//Test1 parent.p; return 0; };
通过正常调用创建出来的实例是一个地址, 如果通过new关键字创建的返回值是一个指针;
如果我们不写拷贝构造函数, 编译器会帮我们自己加拷贝构造函数;
如果需要拷贝的构造函数是有父类的, 那么在写拷贝构造函数的时候要用到继承,需要添加父类的构造函数 , 如果有很多父类, 只要写一个即可:
运行下面代码
Parent(const Parent& parent ):Base(other) { }
使用复制构造函数的时候要特别注意 ,对象的成员变量如果是指针的话, 复制构造函数会复制指针, 当第一个实例被析构的时候, 第二个实例的成员变量指针和被析构的指针指向是一样的, 会导致程序异常, 有一个例子可以做参考, parent.p和p1.p是指向同一个块内存:
运行下面代码
#include <string.h> #include <stdio.h> class Parent { public : int* p; Parent() { } }; int data = 1; int main() { Parent parent; parent.p = &data; Parent p1 = Parent(parent); printf("parent.p address is => %d\n", *(parent.p)); printf("p1.p address is => %d\n", *(p1.p)); printf("parent.p address is => %p\n", parent.p); printf("p1.p address is => %p\n", p1.p); return 0; };
我们也可以把指针类型的成员变量使用private修饰, 或者把拷贝构造函数设置为private, 那么开发者就不会因为手贱随意修改指针所指向的值, 导致软件异常了;
指针的概念
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址,
弄清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。
运行下面代码
(1)int *ptr; (2)char *ptr; (3)int **ptr; (4)int (*ptr)[3]; (5)int *(*ptr)[4];
1。 指针的类型。
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的 类型:
(1)int *ptr; //指针的类型是int *
(2)char *ptr; //指针的类型是char *
(3)int **ptr; //指针的类型是 int **
(4)int (*ptr)[3]; //指针的类型是 int(*)[3]
(5)int *(*ptr)[4]; //指针的类型是 int *(*)[4]
2。指针所指向的类型。
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译 器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符 *去掉,剩下的就是指针所指向的类型。例如:
(1)int *ptr; //指针所指向的类型是int
(2)char *ptr; //指针所指向的的类型是char
(3)int **ptr; //指针所指向的的类型是 int *
(4)int (*ptr)[3]; //指针所指向的的类型是 int()[3]
(5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4]
3。 指针的值,或者叫指针所指向的内存区或地址。
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块 内存区域,就相当于说该指针的值是这块内存区域的首地址。
4。 指针本身所占据的内存区。
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
friend友元
利用友元的写法, 可以让一个类读取另外一个类的私有成员, 不受private和protect的限制:
运行下面代码
#include <string.h> #include <stdio.h> class Object { public: friend void Print(Object *p); Object(int o) :obj(o) {} private: int obj; }; void Print(Object * p) { printf("object.ojb->:%d",p->obj); } int main() { Object* obj = new Object(1); Print(obj); return 0; };
友元破坏了private修饰符, 这个是负面的作用;
但是C++在还提供了这个关键字是有原因的, 如果自己写模块,自己的模块之间用friend关键字, 如果这个类给别人用,别人依然不能访问private修饰符的类成员变量 , 所以友元这种写法对开发者来说是很友好的;
重载<<操作符
运行下面代码
#include <string.h> #include <stdio.h> class Log { public: Log (){ } Log& operator<< (const int i) { printf("output is i %d", i); return *this; } }; int main() { Log lg; lg << 11; return 0; };
利用重载操作符, 可以实现一些简易的写法,方便函数的调用;
相关:
vectorAPI:http://www.cplusplus.com/reference/vector/vector/
本文作者:方方和圆圆
本文链接:https://www.cnblogs.com/diligenceday/p/5782398.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步