c/cpp学习笔记

本文详细介绍了C++的基础知识,包括对象初始化、构造函数与析构函数的规则,指针的使用,封装、继承和多态等面向对象特性,以及函数模板和STL容器(如vector、deque、set和map)的使用方法。
摘要由CSDN通过智能技术生成

基础知识回顾

代码书写应该规范,注释简洁合理。函数变量命名要符合规则。总之要能看懂。

常量定义
#define 宏常量
const 修饰变量

#define weekday 7 //最前面,全局
const int monthday = 30; 

创建.h后缀名的头文件
创建.cpp后缀名的源文件
在头文件中写函数声明
在源文件中写函数定义
(源文件要包含头文件,#include filename.h

指针

#include <iostream>
using namespace std;
 
int main()
{
    int a = 10;
    int *p;
    p = &a;
    cout << "a的地址为"<< &a << endl;
    cout << "a="<< a << endl;

    cout << "指针p为" << p << endl;
    cout << "*p=" << *p << endl;

    *p = 100;
    cout << "a="<< a << endl;

    cout << "指针p为" << p << endl;  
    cout << "*p=" << *p << endl;

    return 0;
}
a的地址为0x5ffe94
a=10
指针p为0x5ffe94
*p=10
a=100
指针p为0x5ffe94
*p=100

什么指针常量、指针函数、函数指针主要看后面一个词,前面是修饰的定语,代表属性

对象的初始化和清理

构造函数和析构函数

对象的初始化和清理工作编译器会强制执行。
如果我们不提供构造和析构函数,编译器会自动提供
编译器提供的构造函数和析构函数是空实现。
构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法: ~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

构造函数调用规则

Person() { }   // 无参构造
Person(int a) { } //有参构造函数
Person(const Person& p) { }//拷贝构造函数
~Person() { }//析构函数

默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝

	//C++中利用new操作符在堆区开辟数据
    //利用new创建的数据,会返回该数据对应的类型的指针
    //深拷贝与浅拷贝	 
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;//拷贝构造函数 
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;
		m_height = new int(*p.m_height);
		}

初始化

初始化列表,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)... {}

//当类中成员是其他类对象时,我们称该成员为 对象成员
//构造的顺序是:先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反

静态成员

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

C++对象模型和this指针

this指针指向被调用的成员函数所属的对象

this指针的用途:

  • 当形参和成员变量同名时,可用this指针来区分(防止命名冲突)
  • 在类的非静态成员函数中返回对象本身,可使用return *this

友元

友元的目的就是让一个函数或者类 访问另一个类中私有成员

友元的关键字为 friend

友元的三种实现

  • 全局函数做友元(将全局函数放进类里,给予friend权限friend void function
  • 类做友元 (将其他类放进类里,给予friend权限friend class 类名
  • 成员函数做友元(将其他类下的成员函数放进类里,给予friend权限friend void function::类名()

运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
也可以用函数重载实现。

  • 加号运算符重载:实现两个自定义数据类型相加的运算
  • 左移运算符重载:输出自定义数据类型
  • 递增运算符重载:实现自己的整型数据
  • 赋值运算符重载
  • 关系运算符重载:让两个自定义类型对象进行对比操作
  • 函数调用运算符重载:称为仿函数,可以灵活使用

面向对象

封装的意义

封装是C++面向对象三大特性之一
语法: class 类名{ 访问权限: 属性 / 行为 };

封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

封装意义一: 在设计类的时候,属性和行为写在一起,表现事物
封装意义二: 类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  1. public 公共权限
  2. protected 保护权限
  3. private 私有权限

继承 Inheritance

继承是面向对象三大特性之一
类中,下级别的成员除了拥有上一级的共性,还有自己的特性。
利用继承的技术,减少重复代码

  class A : 继承方式 B;
  
  A 类称为子类 或 派生类
  B 类称为父类 或 基类
  继承方式一共有三种:  
  * 公共继承 public
  * 保护继承 protected
  * 私有继承 private

  访问父类同名成员 需要加作用域

利用虚继承解决菱形继承的问题 class A : virtual 继承方式 B;

多态 polymorphic

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

利用虚函数实现晚绑定

//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
//重写:函数返回值类型  函数名 参数列表 完全一致称为重写

//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别: 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){}

纯虚析构语法: virtual ~类名() = 0;
类名::~类名(){}

函数模板

  • C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板

  • C++提供两种模板机制:函数模板类模板

语法:

template<typename T>
函数声明或定义
//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
//具体化优先于常规模板

//普通函数模板
template<class T>
bool myCompare(T& a, T& b)

template<> bool myCompare(Person &p1, Person &p2) //具体化

语法:

template<typename T>
类

当类模板碰到继承时,需要注意一下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

STL

  • STL(Standard Template Library,标准模板库)
  • STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)
  • 容器算法之间通过迭代器进行无缝连接。
  • STL 几乎所有的代码都采用了模板类或者模板函数

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与算法之间的胶合剂。
  4. 仿函数:行为类似函数,可作为算法的某种策略。
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
  6. 空间配置器:负责空间的配置与管理。

容器

容器

vector

动态数组,在超出内存时会自动重新分配内存。还可以自定义数据类型,使用起来很方便。
vector容器可以嵌套使用,父容器不改变子容器内存,父容器存储子容器副本或指向子容器的指针。
编译器为了避免频繁清空内存分配内存操作,所以往往会预分配一定的容量给vector。

    vector<int> v1; //无参构造
    vector<T> v;                   //采用模板实现类实现,默认构造函数
    vector(v.begin(), v.end());    //将v[begin(), end())区间中的元素拷贝给本身。
    vector(n, elem);               //构造函数将n个elem拷贝给本身。
    vector(const vector &vec);     //拷贝构造函数。
 
    vec1.insert(vec1.end(),5,3);    //从vec1.back位置插入5个值为3的元素
 
    vec1.pop_back();              //删除末尾元素
    vec1.push_back(n);              //增添末尾元素n
 
    vec1.erase(vec1.begin(),vec1.end());//删除之间的元素,其他元素前移
    vec1.clear();                 //清空元素 
    
    cout<<(vec1==vec2)?true:false;  //判断是否相等==、!=、>=、<=...
 
    vector<int>::iterator iter = vec1.begin();    //获取迭代器首地址
     
    vector<int>::const_iterator c_iter = vec1.begin();   //获取const类型迭代器

    for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++) {} //循环遍历

    int size = vec1.size();       //元素个数 
    bool isEmpty = vec1.empty();  //判断是否为空
    capacity();                  //容器的容量
    resize(int num);              //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
                                  //如果容器变短,则末尾超出容器长度的元素被删除。
    resize(int num, elem);    //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。//如果容器变短,则末尾超出容器长度的元素被删除
    vec1.swap(vec2)       //将vec1和vec2元素互换----->可以解决resize带来的内存浪费-->vec1.swap(vec1)
    reserve(int len);    //容器预留len个元素长度,预留位置不初始化,元素不可访问。

deque

deque,从前后两端都可以进行数据的插入和删除操作,同时支持数据的快速随机访问。
类似vector。支持随机访问

deque与vector区别:

  • vector对于头部的插入删除效率低,数据量越大,效率越低
  • deque相对而言,对头部的插入删除速度回比vector快
  • vector访问元素时的速度会比deque快,这和两者内部实现有关
  • deque没有容量的概念!deque是由多个固定大小的块组成的,这些块不一定是连续的。
    deque<int> d1; //无参构造
    deque<T> d;                   //采用模板实现类实现,默认构造函数
    deque(v.begin(), v.end());    //将v[begin(), end())区间中的元素拷贝给本身。
    deque(n, elem);               //构造函数将n个elem拷贝给本身。
    deque(const deque &deq);     //拷贝构造函数。
 
    deq1.insert(deq1.end(),5,3);    //从vec1.back位置插入5个值为3的元素
 
    deq1.pop_back();       //删除末尾元素
    deq1.push_back(n);     //增添末尾元素n
    deq1.push_front(n);    //增添头部元素n 
    deq1.pop_front();      //删除头部元素
 
    deq1.erase(deq1.begin(),deq1.end());//删除之间的元素,其他元素前移
    deq1.clear();                 //清空元素 
    
    cout<<(deq1==deq2)?true:false;  //判断是否相等==、!=、>=、<=...
 
    deque<int>::iterator iter = deq1.begin();    //获取迭代器首地址
     
    deque<int>::const_iterator c_iter = deq1.begin();   //获取const类型迭代器

    for (deque<int>::iterator it = deq.begin(); it != deq.end(); it++) {} //循环遍历

    int size = deq1.size();       //元素个数 
    bool isEmpty = deq1.empty();  //判断是否为空
    resize(int num);              //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
                                  //如果容器变短,则末尾超出容器长度的元素被删除。
    resize(int num, elem);    //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。//如果容器变短,则末尾超出容器长度的元素被删除
    vec1.swap(vec2)       //将vec1和vec2元素互换----->可以解决resize带来的内存浪费-->vec1.swap(vec1)
    reserve(int len);    //容器预留len个元素长度,预留位置不初始化,元素不可访问。

    sort(iterator beg, iterator end) //对beg和end区间内元素进行排序

list

STL中的链表是一个双向循环链表

功能: 将数据进行链式存储

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的

链表的组成:链表由一系列结点组成

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素

list的缺点:

  • 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

list语法基本同vector

  • insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
  • insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
  • insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
    list容器中不可以通过[]或者at方式访问数据
  • reverse(); //反转链表
  • sort(); //链表排序
存储方式插入和删除的时间复杂度访问特定位置元素的时间复杂度
list使用双向链表实现,内存不连续任意位置操作时间复杂度为 O(1)O(n),因为需要从链表头或尾开始遍历
deque双端队列,使用多个固定大小的块来存储元素两端时间复杂度为 O(1)。在中间位置操作则可能较慢接近 O(1),不如 vector 高效
vector动态数组,元素在内存中连续存储尾部操作时间复杂度为 O(1),中间或开始位置操作时间复杂度为 O(n)访问特定位置元素的时间复杂度为 O(1)

set

所有元素都会在插入时自动被排序(默认从小到大,可自定义,对于自定义数据类型,set必须指定排序规则才可以插入数据)
set/multiset属于关联式容器,底层结构是用二叉树实现。

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝

map

pair对组,成对出现的数据,利用对组可以返回两个数据

  • pair<type, type> p ( value1, value2 );
  • pair<type, type> p = make_pair( value1, value2 );

map/multimap属于关联式容器,底层结构是用二叉树实现。

  • map中所有元素都是pair
  • pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
  • map会按照key的值内部进行排序,但是保持其键值对的对应关系不变。
  • 可以根据key值快速找到value值
  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素

map保存的是键值对,所以前面的int类型数据(key)并不代表其位置。比方说,我们将其中的int修改为float也是可以的。

	//第一种:用insert函数插入pair数据:
	map<int, string> my_map;
	my_map.insert(pair<int, string>(1, "first"));
	//第二种:用insert函数插入value_type数据:
	my_map.insert(map<int, string>::value_type(3, "third"));
	//第三种:用数组的方式直接赋值:
	my_map[5] = "fifth";
	

string

string是C++风格的字符串,而string本质上是一个类
char * 是一个指针
string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器

stack

先进后出,栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为


构造函数:
 stack<T> stk;         //stack采用模板类实现, stack对象的默认构造形式
 stack(const stack &stk);            //拷贝构造函数

赋值操作:
 stack& operator=(const stack &stk);           //重载等号操作符

数据存取:
 push(elem);      //向栈顶添加元素
 pop();                //从栈顶移除第一个元素
 top();                 //返回栈顶元素

大小操作:
 empty();            //判断堆栈是否为空
 size();               //返回栈的大小

queue

先进先出
队列不提供迭代器,更不支持随机访问

  • 入队 — push
  • 出队 — pop
  • 返回队头元素 — front
  • 返回队尾元素 — back
  • 判断队是否为空 — empty
  • 返回队列大小 — size
  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值