复习c语言:第五章
类
c语言中的类, 只要是面向对象的语言都会有类的概念, 只要了解过面向对象编程, 那么c语言中的类也是一样的 , c语言的面向对象很像java, 相对于javascript中的面向对象, 可以实现真正的意义上的私有属性, 在js中只能通过闭包方式的实现:
运行下面代码
#include <stdio.h> #include <string.h> class obj { public: char name[10]; int age; private: char gender; }; int main() { obj o; o.age = 10; strcpy(o.name,"hehe"); printf("%s age is %d",o.name, o.age); return 0; }
类的方法上可以使用this, this为当前元素的指针, 所以c语言也是非常灵活的啊:
运行下面代码
#include <stdio.h> #include <string.h> class obj { public: char name[10]; int age; void test() { this->gender = '1'; } void test1() { this->gender = '0'; } void showRes() { printf("the result is : %c\n", this->gender); } private: char gender; }; int main() { obj o; o.showRes(); o.test(); o.showRes(); o.test1(); o.showRes(); return 0; }
在class类中, 可以定义构造函数, 构造函数的名字和class类名是一模一样的,而且构造函数支持重载:
运行下面代码
#include <stdio.h> #include <string.h> class obj { public: obj() { strcpy(this->name,"nice"); this->age = 100; } obj(const char* name, int age) { strcpy(this->name,name); this->age = age; } char name[10]; int age; private: char gender; }; int main() { obj o; printf("%s age is %d\n",o.name, o.age); //使用有参构造函数的形式创建对象; obj o1("new object",1000); printf("%s age is %d",o1.name, o1.age); return 0; }
析构函数只能有一个,而且析构函数没有参数, 析构函数在对象被回收的时候会被执行, 实际的用处是:当对象被回收以后, 要把对象申请到的内存空间清空:
运行下面代码
#include <stdio.h> #include <string.h> class obj { public: obj() { strcpy(this->name,"nice"); this->age = 100; } char name[10]; int age; ~obj() { printf("析构中!"); } private: char gender; }; int main() { obj o; return 0; }
使用new和delete关键字,申请内存和清空内存
使用new和delete关键字,申请内存和清空内存:
运行下面代码
#include <stdio.h> #include <string.h> int main() { int* p = new int; *p = 100; printf("p is %d\n", *p); return 0; }
可以申请一堆内存:
运行下面代码
#include <stdio.h> #include <string.h> int main() { int* p = new int[40]; p[0] = 100; printf("p is %d\n", p[0]); p[39] = 20; printf("40: %d", p[39]); return 0; }
delete关键字:
运行下面代码
#include <stdio.h> #include <string.h> int main() { int* p = new int[40]; p[0] = 100; printf("p is %d\n", p[0]); p[39] = 20; printf("40: %d", p[39]); //使用delete清空占用的内存 delete []p; return 0; }
使用new和delete还有一个巨大的用处, new一个class类的话, 这个类会被自动实例话, delete一个类,会自动执行对象的析构方法, 而这个是malloc和free无法做到的:
运行下面代码
#include <stdio.h> #include <string.h> class obj { public: obj() { printf("构造ing!\n"); } ~obj() { printf("析构中!\n"); } }; int main() { obj* o = new obj(); delete o; return 0; }
构造函数的继承
构造函数的继承使用 “:”, 如果要让B继承A, 要写成
运行下面代码
class B : public A
那么B的实例,就可以使用实例A的方法或者实例A的属性, 以下代码中base为父类,Obj为子类, Obj的实力可以调用base上面的方法和属性:
运行下面代码
#include <stdio.h> #include <string.h> class base { public: char name[10] = "abc"; char* getName() { return this->name; } }; class Obj : public base { public: int age = 10; }; int main() { Obj o; printf("the name is %s", o.getName()); return 0; }
在子类中也可以定义同名的方法或者属性,父级中同名的属性和方法会被自动覆盖,可以在子类中调用父类的方法和属性, 使用父类的名字.父类的方法():
运行下面代码
#include <stdio.h> #include <string.h> class base { public: char name[10] = "abc"; char* getName() { return this->name; } }; class Obj : public base { public: char* getName() { strcat(base::getName(), "child"); return this->name; } }; int main() { Obj o; printf("the name is %s", o.getName()); return 0; }
virtual关键字
如果我们使用定义了一个父类指针,但是实例化的时候是实例化子类,在调用方法或者属性的时候, 调用的是父类的方法:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> class base { public: void print() { printf("my name is %s\n", "abc"); } }; class Obj : public base { public: void print() { printf("my name is %s\n", "def"); } }; int main() { base* o = new Obj(); o->print(); delete(o); return 0; }
这种情况下,我们就要使用“虚拟继承==>virtual”关键字, 只要给父类添加虚拟继承即可, 子类会自动继承父类的虚拟继承关键字,只要子类添加了虚拟继承关键字, 无论构造函数的指针是怎么变,当调用的方法或者属性, 都只是调用该实例的方法和属性:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> class base { public: virtual void print() { printf("my name is %s\n", "abc"); } }; class Obj : public base { public: void print() { printf("my name is %s\n", "def"); } }; int main() { base* o = new Obj(); o->print(); delete(o); return 0; }
使用继承的时候要注意一点, 要给父类的析构函数添加virtual关键字, 在构造指针和实例对象不同的情况下, 可能导致子类的析构函数没有被调用;
以上的代码实现的是单继承, 我们可以实现多重继承, 让子类拥有很多的父类, 即使在Javascript,继承用的还是少呢, 所以多重继承只要了解一下即可:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> class base { public: virtual void print() { printf("my name is %s\n", "abc"); } }; class base1 { public: virtual void print1() { printf("my name is %s\n", "def"); } }; class base2 { public: virtual void print2() { printf("my name is %s\n", "ghi"); } }; class Obj : public base , public base1 , public base2 { public: }; int main() { Obj* o = new Obj(); o->print(); o->print1(); o->print2(); delete(o); return 0; }
虚构函数可以作为接口, 这种全部是虚构函数的类,我们称之为纯虚类或者抽象类, 抽象类无法被实例化, 只能被继承后再实例化;
拷贝构造函数
拷贝构造函数, 当我们创建一个构造函数的时候, 系统默认会添加一个拷贝构造函数, 我们也可以自己实现一个拷贝构造函数, 覆盖系统默认的拷贝构造函数, 调用拷贝构造函数的方法非常多,比如想创建一个o1实例, 拷贝自o:
运行下面代码
Obj o1 = Obj(o); Obj o1(o); Obj o1 = o; Obj* p = new Obj(o);
拷贝构造函数的使用方式:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> class Obj { public: int a; int b; Obj() { this->a = 0; this->b = 1; } }; int main() { Obj o; printf("a=%d,b=%d\n", o.a, o.b); //调用拷贝构造函数, 复制一个o对象; Obj o1(o); o1.a = 1; printf("a=%d,b=%d\n", o1.a, o1.b); return 0; }
或者自己定一个方法或者函数, 利用函数传参的特性,自动复制该对象:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> class Obj { public: int a; int b; Obj() { this->a = 0; this->b = 1; } Obj copy(Obj o) { return o; } }; int main() { Obj o; printf("a=%d,b=%d\n", o.a, o.b); Obj o1 = o.copy(o); o1.a = 1; printf("a=%d,b=%d\n", o1.a, o1.b); return 0; }
我们可以自己定义拷贝构造函数, 那么系统默认的拷贝构造函数会被覆盖:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> class Obj { public: int a; int b; Obj() { this->a = 1; this->b = 2; } //手动创建拷贝构造函数 Obj(const Obj& o) { this->a = o.a; this->b = o.b; } }; int main() { Obj o; printf("a=%d,b=%d\n", o.a, o.b); //Obj o1 = Obj(o); Obj o1 = o; //要区别 o1 = o;一个是复制, 一个是赋值; //Obj* p = new Obj(o); printf("a=%d,b=%d\n", o1.a, o1.b); return 0; }
使用拷贝构造函数的时候要注意, 拷贝构造函数, 拷贝的时候会复制对象的指针, 这个将会导致,两个实例指向同一个内存地址, 在析构的时候会出现错误❌:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> int getLen(char *p) { int i=0; while(p[i]!=0){ i++; } return i; } class Obj { public: char* text; Obj(char* t) { this->text = new char[getLen(t)+1]; strcpy(this->text,t); } char* getText() { return this->text; } ~Obj() { delete this->text; } }; int main() { Obj o("text"); printf("the str is %s\n", o.getText()); //当复制了对象o, 会导致o1的text指针和o的text指针指向的地址一模一样 //析构的时候就会出错; Obj o1(o); return 0; }
需要自己重新写拷贝构造函数, 在拷贝函数中实现指针的复制:
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> int getLen(char *p) { int i=0; while(p[i]!=0){ i++; } return i; } class Obj { public: char* text; Obj(char* t) { this->text = new char[getLen(t)+1]; strcpy(this->text,t); } char* getText() { return this->text; } Obj(const Obj& o) { //不能够调用o.getText(),会报错; //char* text = o.getText(); char* text = o.text; int len = getLen(text); this->text = new char[len]; } ~Obj() { delete this->text; } }; int main() { Obj o("text"); printf("the str is %s\n", o.getText()); Obj o1(o); return 0; }
命名空间
命名空间namespace的使用, 为了避免代码可能出现冲突, 使用命名空间是个好主意:
头文件:c1.h
运行下面代码
#include <stdio.h> #include <string.h> #include <stdlib.h> namespace nono{ char area[] = "xiamen"; }
内容文件: c1.c
运行下面代码
#include "c1.h" namespace nono{ int temp = 100; } int main() { printf("temp is %d, str is %s\n",nono::temp,nono::area); return 0; }
可以通过using namespace导入命名空间, 头文件还是c1.h,内容为c2.c, 导入的命名空间可以直接使用, 使用的时候不比加前缀:
运行下面代码
#include "c1.h" using namespace nono; namespace nono{ int temp = 100; } int main() { printf("temp is %d, str is %s\n",temp,area); return 0; }
template模版(泛型)
泛型, 原来在c语言中已经有泛型了, 泛型的关键字为template, 如果需要实现一个 字符串数组的排序或者数字数组的排序,可以使用同一个函数, 定义泛型的代码必须放在使用泛型的函数之前, 他们之间不能隔别的代码, 否者编译器的时候会报错:
运行下面代码
#include "c3.h" template<typename T> //T max(T array[], int len) { T max(T* array, int len) { int i; T max = array[0]; for(i = 0; i<len; i++) { printf("%d\n", array[i]); if( array[i] > max ) { max = array[i]; } } return max; } int main() { int arr[] = {4,5,6,4}; int m = max<int>(arr, 4); printf("the max is %d\n", m); return 0; }
vector和list
vector是标准库中的类, 在命名空间std下, vector是c语言提供的数组, 通过#include <vector>使用
vector对象有以下方法:
运行下面代码
c.clear() 移除容器中所有数据。
c.empty() 判断容器是否为空。
c.erase(pos) 删除pos位置的数据
c.erase(beg,end) 删除[beg,end)区间的数据
c.front() 传回第一个数据。
c.insert(pos,elem) 在pos位置插入一个elem拷贝
c.pop_back() 删除最后一个数据。
c.push_back(elem) 在尾部加入一个数据。
c.resize(num) 重新设置该容器的大小
c.size() 回容器中实际数据的个数。
c.begin() 返回指向容器第一个元素的迭代器
c.end() 返回指向容器最后一个元素的迭代器
vector:
运行下面代码
#include <stdio.h> #include <string.h> #include <vector> using namespace std; int main () { vector<int> ver; ver.push_back(100); ver.push_back(101); ver.push_back(102); ver.push_back(103); int i = 0; int len = ver.size(); for(i=0; i<len; i++) { printf("%d\n", (int)ver[i]); } printf("for end\n"); ver.pop_back(); ver.pop_back(); len = ver.size(); for(i=0; i<len; i++) { printf("%d\n", (int)ver[i]); } return 0; }
除了使用for循环,循环vecotr对象, 还可以使用迭代器循环vector对象:
运行下面代码
#include <stdio.h> #include <string.h> #include <vector> int main () { std::vector<int> ver; ver.push_back(100); ver.push_back(101); ver.push_back(102); ver.push_back(103); std::vector<int>::iterator p; p = ver.begin(); for(; p!=ver.end(); p++) { printf("%d\n",*p); } return 0; }
标准模版库还有提供list, list就是c中的双向列表, 基本上所有vector能够实现的list,也可以实现:
运行下面代码
begin 返回指向容器起始位置的迭代器
end 返回指向容器末尾位置的迭代器
empty 判断是否为空
max_size 返回 forward_list 支持的最大元素个数
assign 将新的内容赋值给容器
emplace_front 在容器开头构造及插入一个元素
push_front 在容器开头插入一个元素
pop_front 删除第一个元素
emplace_after 构造及插入一个元素
insert_after 插入元素
erase_after 删除元素
swap 交换内容
resize 改变有效元素的个数
clear 清空内容
splice_after 使元素从一个正向列表移动到另一个正向列表
remove 删除值为指定值的元素
remove_if 删除满足指定条件的元素
unique 删除重复值
merge 合并已排序的正向列表
sort 为容器中的所有元素排序
reverse 反转元素的顺序
运行下面代码
#include <stdio.h> #include <string.h> #include <list> using namespace std; int main () { list<int> l; l.push_back(100); l.push_back(101); printf("the size is %d\n", (int)l.size()); list<int>::iterator it; it = l.begin(); for( ; it!=l.end() ;it++ ) { printf("%d\n",*it); } return 0; }
list和vector的使用场景:
vector 表示一段连续的内存区域,每个元素被顺序存储在这段内存中,对vector的随机访问效率很高,但对非末尾元素的插入和删除则效率非常低。
list 表示非连续的内存区域并通过一对指向首尾元素的指针双向链接起来,插入删除效率高,随机访问效率低;
map
map是键值对, 每一个键对应了一个值, 而且map的键只能唯一, 这种数据结构我们称之为map
vector和list只能保存一组数据, 只能对一组数据进行操作, map用来保存数据的时候更加有用, map能够同时保存一对值,比如 ("name", "nono"), ("age","100"):
运行下面代码
begin 返回指向容器起始位置的迭代器(iterator)
end 返回指向容器末尾位置的迭代器
size 返回有效元素个数
insert 插入元素
erase 删除元素
swap 交换内容
clear 清空内容
find 通过给定主键查找元素
count 返回匹配给定主键的元素的个数
map的使用案例:
运行下面代码
#include <stdio.h> #include <string.h> #include <map> using namespace std; int main() { map<int,int> foo; foo.insert(map<int,int>::value_type(1, 1)); foo.insert(map<int,int>::value_type(2, 2)); map<int,int>::iterator it = foo.begin(); for(; it!=foo.end(); it++) { printf("key is %d\n", it->first); printf("value is %d\n", it->second); printf("\n"); } return 0; }
map对象是基于vector和list之上的一种数据类型, 我们通过vector或者list可以自己实现一个map以及相关的方法
参考:
c++手册:http://classfoo.com/ccby/article/acZKb
eof
本文作者:方方和圆圆
本文链接:https://www.cnblogs.com/diligenceday/p/6035078.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步