首页 > 编程语言 >C++笔记

C++笔记

时间:2024-06-10 23:29:40浏览次数:26  
标签:const 函数 int 笔记 C++ 类型 模板 指针

c++

一、基础

(一)C++初识

1.注释
//  1.单行注释,上方或末尾 
/*
    2.多行注释,上方
*/

/*
	3.main是一个程序的入口
	每个程序都有必须有这么一个函数
	有且仅有一个
	默认return 0,程序状态正常
*/
//	;是语句结束
//	;;;;多个空语句
//	l+(r-1)/2比(l+r)/2比l+r>>1 更安全,精度更高,整数溢出
2.变量
数据类型 变量名 = 变量初始值
3.常量
作用:用于记录程序中不可更改的数据

1. #define 宏常量: #define 常量名 常量值
- 通常在文件上方定义,表示一个常量,编译之前,不安全

2. const修饰的变量: const 数据类型 常量名 = 常量值
- 通常在变量定义前加关键字const,修饰该变量,不可修改
4.关键字
  • 不要用关键字给变量或者常量起名称
5.标识符命名规则
- 标识符不能是关键字
- 只能由字母、数字、下划线组成
- 第一个字母必须为字母或下划线
- 标识符中字母区分大小写
- 函数体以外不用下划线开头
- 不能连续两个下划线或者下划线+大写字母开头,这些被保留
- 变量一般用小写字母
- 自定义类名一般以大写字母开头
- 多单词一般下划线或者后面首字母大写

(二)数据类型

1.整型
short 		2字节
int			4字节
long		win 4字节,linux 4字节(32位),8字节(64位)
long long	8字节
可以自定义
unsigned	int		无符号整形
字面值常量
    - 0开头八进制
    - 0x开头16进制
    - 默认不加,int
    - l或L,long
    - ll或LL,long long
    - u或U,unsigned 可以和L或LL组合使用
    - 9527uLL
2.sizeof关键字
  • 统计数据类型所占内存大小
seizeof 数据类型/变量
3.实型(浮点型)
  • 默认为double,float后加f,小数默认显示6位有效数字
float	4字节		7位有效
double	8字节		15-16位有效
  • 科学计数法
float f1 = 3e2		//3*10^2
float f2 = 3e-2		//3*0.1^2
4.字符型
char ch = 'a';
//单引号内只能有一个字符,不能是字符串
ch = 97
  • c和c++中只占用1个字节
  • 不存储字符本身,而是存放对应ASCLL编码到存储单元
  • 可以直接用ASCLL给字符变量赋值
  • 0-31控制字符,32-126打印字符
5.转义字符
  • 用于表示一些不能显示出来的ASCLL字符
\t 8个空格
6.字符串型
  • c风格字符串:
char 变量名[] = "字符串值"
  • c++风格字符串:
#include <string>
string 变量名 = "字符串值"
7.布尔类型
  • true 真(本质是1,非0也为真)
  • false 假 (本质是0)
  • 占用1个字节
8.数据的输入
cin >> 变量
cin.get()	//等待键盘输入

(三)运算符

1.算术运算符
  • 两个小数不可以做取模运算,只能是整型
  • a%b=c,-a%b=-c
2.赋值运算符
  • 左边必须是可修改的左值
  • 满足右结合律,可以连续赋值
3.关系运算符
  • 算术运算符优先级一般高于关系运算符
  • 返回值为布尔类型
4.逻辑运算符
  • 如果将算术类型的对象作为逻辑运算符的操作数,那么0为false,非0为true
  • 逻辑与和逻辑或短路求值
5.条件运算符
  • 可连续使用,满足右结合律
  • 等同于if…else…
6.位运算符
  • 之前运算符以字节为单位操作,位运算符具体操作每一位
(1)移位运算符
  • 较小的整数类型(char、short、bool)会自动提升成int类型再做移位,结果为int
  • 左移运算符将操作数左移之后,右侧补0
  • 右移无符号补0,有符号看情况
(2)位逻辑运算符
  • 先提升到int,再操作
  • 异或,aa=0,a0=a,例如找只出现一个的数
7.类型转换
  • 初始化,赋值,右值转换左值类型
  • 隐式类型转换,长度较小的转成长度较大的类型
  • 应避免大转小
  • 强制类型转换
c语言风格:
(类型名称)值
c++风格:
类型名称(值)
c++强制类型转换运算符:
通常使用static_cast<类型名称>(值)

(四)流程控制语句

  • swith
swith(表达式,先求值,再转换成整形){
case 整型值或常量表达式1:
...
default:

}
  • c++11范围for循环
for(声明:序列表达式)
	语句
for(int num : {3,6,8,10}){
	cout<<num<<endl;
}

(五)复合数据类型

1.数组
(1)定义

数据类型 数组名[元素个数]

  • 元素个数必须确定
  • 对数组做初始化后,默认补0,未做初始化,数值都是未定义的(0xcccc,烫)
(2)访问和遍历
sizeof(a)/sizeof(a[0])
(3)多维数组
  • 列数不能省略

  • 遍历

    for(auto& row : ia){
    	for(auto num : row){
    	
    	}
    }
    
(4)数组的简单排序
  • 选择排序
  • 冒泡排序
2.模板类vector简介
#include<vector>
using namespace std;

//默认初始化,空的容器
vector<int>		v1;	

//列表初始化(拷贝初始化)
vector<char>	v2 = {'a','b'};
vector<char>	v3{'a','b'};

//直接初始化
vector<short>	v4(5);//5个长度,默认0
vector<long>	v5(5,100);//5个长度,默认100

//遍历
v5.size();	//v的长度

//添加元素
v5.push_back(69);

c++11增加了array模板类,固定长
3.字符串
(1)标准库类型string
#include<string>
using namespace std;

//默认初始化,空字符
string s1;

//拷贝初始化
string s2 = s1;
string s3 = "Hello";

//直接初始化
string s4("Hello");
string s5(8,'h');	//8个h连在一起

//长度
s4.size()

//字符串拼接
s3+s4
- 两个字符串字面值常量,不能相加
- 多个连续相加,左结合律,每次相加必须保证至少有一个string对象
    
//比较字符串
- 长度相同,字符也相同,相等
- 长度不等,较短字符串每个字符和较长对应位置字符相同,则较短字符串小于较长字符串
- 从某一位置开始不同,则比较这两个字符的ASCLL码,判别大小
(2)字符数组(C风格字符串)
  • char[]类型
  • c语言规定:必须以空字符结束。空字符的ASCLL码为0,专门用来标记字符串的结尾,在程序中写作’\0’
char str1[3] = {'h','e','l'};	//str1并不是一个字符串
char str2[4] = {'h','e','l','\0'}	//str2是一个字符串
char str3[] = "hel";	//默认加空字符,长度为4
(3)读取输入的字符串
①使用输入操作符读取单词
  • 使用iostream内置的cin对象,调用重载的输入操作符>>来读取键盘输入
  • 特点:忽略开始的空白符,遇到下一个空白符(空格、回车、制表等)就会停止。
cin >> str1 >> str2
②使用getline读取一行
  • 直接读取一整行输入信息
  • getline函数有两个参数:一个是输入对象流cin,另一个是保存字符串的string对象;它会一直读取输入流中的内容,直到遇到换行符为止,然后把所有内容保存到srting对象中。(最后的换行符标识结束,直接丢掉)
string str;
getline(cin,str);
③使用get读取字符
  • 调用cin.get()函数,不传参数,得到一个字符赋给char类型变量
  • 将char类型变量作为参数传入,将捕获的字符赋值给它,返回的是istream对象
char ch;
ch = cin.get();	//将捕获到的字符赋值给ch
cin,get(ch);	//直接将ch作为参数传给get
  • get函数还可以读取一行内容,这种方式跟getline很相似,也可以读取一整行内容,以回车结束。主要区别在于,它需要把信息保存在一个char[]类型的字符数组中,调用的是cin的成员函数
char str[20];
cin.get(str,20)l
  • get函数两个参数:一个是保存信息的字符数组,另一个是字符数组的长度
(4)简单读写文件
#include<iostream>
#include<fstream>
#include<string>
using namespace std;

int main(){
    ifstream input("input.txt");
    ofstream output("output.txt");

/*    string word;
    while(input >> word){
        cout << word << endl; 
    }
*/
/*    string line;
    while(getline(input,line)){
        cout << line <<endl;
    }
*/
    char ch;
    while (input.get(ch))
    {
       // cout << ch << endl;
       output << ch << endl;
    }
    
}
4.结构体
(1)结构体的声明,初始化
struct obj
{
	type1 obj1;
	type2 obj2;
	...
}o1,o2={};

obj o3 = {};
obj o4 = o3;
  • 创建结构体变量对象时,可以直接用定义好的结构体名作为类型;相比c语言中的定义,这里省略了关键字struct
  • 如果没有赋初始值,那么所有数据将被初始化成默认值;算术类型默认值是0
(2)访问,结构体数组
  • 成员运算符——点号 .
5.枚举
(1)定义
  • 枚举类型enum是C++提供了另一种创建符号常量的方式,可以替代const
enum week
{
	Mon, Tue, Wed = 10, Thu, Fri, Sat, Sun
};
  • 与结构体不同,枚举类型内只有有限个名字,各自代表一个常量,被称为“枚举量”
  • 默认情况下,会将整数值赋给枚举量
  • c枚举量默认从0开始,每个枚举量依次加1;c++允许扩大范围,可以赋更多值。如上面前五个分别对应着0,1,10, 11,12,…
  • 可以通过对枚举量赋值,显式的设置每个枚举量的值
Week w1 = Mon;
Week w2 = 3;//错误,类型不匹配
Week w3 = Week(3);//正确,强制类型转换
6.指针
  • 是一种特殊数据类型,是一种间接访问对象的方法
  • 所占字节与操作系统有关,64位为8字节
  • 内存中,低位在前,高位在后
(1)定义
  • 类型* 指针变量;
  • 类型指的是指针所指对象的数据类型
(2)用法
  • 取地址运算符&
  • 解引用运算符*
  • (*p).value == p->value
(3)特殊用法
①无效指针
  • 定义一个指针,若未初始化,那么它内容是不确定的(比如0xcccc)。可能指向系统核心区域。也叫野指针
②空指针
  • 空指针不指向任何对象
  • 使用字面量nullptr,这是c++11引入的方式,推荐使用
  • 使用预处理变量NULL,这是老版本的方式,本质是0
  • 直接使用0值(被认为不被使用的地址,“0”地址),不能用int型赋值
③void* 指针
  • 可以存放任意对象类型的地址
  • 只能存放地址,不能解引用,不知道具体类型取多少字节
  • 一般只用来比较地址、或者作为函数的输入输出
(4)指向指针的指针
  • 指向指针的指针,**二级指针,三级指针…
  • **二次解引用,三次…
(5)指针和const
①指向常量的指针
  • 指针是变量,不能修改指向的数据对象,const在类型前
const int c1 = 10, c2 =25;
int* pc = &c1;	//错误,类型不匹配
const int* pc = &c1;
pc = &c2;
②指针常量(const)指针
  • 指针本身是一个数据对象,所以也可以区分变量和常量
  • 保存的地址不能改,永远指向同一个对象
  • 需要在*后、标识符前加上const
int* const cp = &i;

可以两个const
const int* const ccp = &i;
(6)指针和数组
①数组名
  • 编译器一般都会把数组名转换成指针,指向第一个元素,可以用数组名给指针赋值
②指针运算
  • 指针+1,根据数据类型跳到下一个对象
  • 范围for循环本质就是arr+1
③指针数组和数组指针
  • 指针数组:一个数组,它的所有元素都是相同类型的指针
  • 数组指针:一个指针,指向一个数组的指针
int* pa[5];		//指针数组
int(* ap)[5];	//指向有五个元素的数组的指针
ap = arr;		//错误,类型不匹配
ap = &arr;		//正确
*ap == arr
7.引用
(1)用法
  • 在c++中,起别名,叫做引用
  • 在变量名前加上"&",int& ref = a
  • 引用必须被初始化,绑定后就不能修改
  • 不存储数据,不会被分配空间编译器直接翻译成所绑定的原始变量
  • 引用的引用,int& rref =ref
(2)对常量的引用
const int zero = 0;
int& cref = zero;	//错误,不能用普通引用去绑定常量
const int& cref = zero;	//正确,常量的引用

常量引用初始化要求宽松,只要是可以转换成它指定类型的所有表达式,都可以用来做初始化
const int& cref2 = 10;	//正确,可以用字面值常量做初始化
int i = 35;
const int& cref3 = i;	//正确,可以用一个变量做初始化,拷贝
double d = 3.14;
const int& cref4 = d;	//正确,d会先转成int 类型,引用绑定的是一个临时量
(3)指针和引用
  • 引用也是一种间接访问方式
  • 引用和指针常量相似,区别在分配内存
  • 绑定指针的引用 int* & p
  • 引用的本质是c++引入的一种语法糖,它是对指针的一种伪装

(六)函数

1.函数基本知识
  • 函数返回类型不能是数组或者函数
(1)局部变量的生命周期
  • 全局变量,程序结束时销毁
  • 局部变量
    • 自动对象:程序执行到变量定义语句时创建,程序运行到块末尾时销毁,栈区
      • 形参也是一种自动对象
      • 默认值是随机的,比如0xcccc
    • 静态对象:static,局部静态对象
      • 生命周期延长,程序结束时才销毁
      • 只有局部的作用域,在块外依然是不可见的
      • 初始化只会执行一次
      • 和自动对象存放内存区域不同
      • 基本类型会默认初始化为0
(2)函数声明
  • 也叫函数原型
  • 一般情况下,把函数声明放在头文件中会更加方便
int square(int a);
int square(int);	//可以省略形参名字
2.参数传递
  • 值传递,值拷贝,包括传指针
  • 传引用,避免拷贝,如果不修改可以用常量引用做形参,还可以扩大传参范围
  • 数组形参,数组不允许做直接拷贝
void printArray(const int*);
void printArray(const int[]);	//两种方式一样,解析成指针,指定长度无效
长度如何确定
- 规定结束标记
- 把数组长度作为形参
使用数组引用作为形参
void printArry(const int(& arr)[5])
  • 可变形参
    • 省略号(…),兼容C语言的用法,只能出现在形参列表的最后一个位置
    • 初始化列表initializer_list,跟vectr类似,也是一种标准库模板类型;initializer_list对象中的元素只能是常量值,不能更改
    • 可变参数模版,这是一种特殊的函数
3. 返回类型
(1)返回类型
  • 返回类型为void时,会自动加上return返回

  • 原理:函数会在调用点创建一个“临时量”,用来保存函数调用的结果。当return语句返回时,就会用返回值去初始化这个临时量。所以返回值的相关规则,跟变量或者形参的初始化是一致的。

  • const string& longStr(const string& str1, const string& str2)
    
  • 函数返回引用类型时,不能返回局部对象的引用;同样道理,也不应该返回指向局部对象的指针

  • 返回类对象后连续调用,调用运算符()和点. 同级,满足左结合律

  • 返回数组指针

    int(*fun(int x))[5];	//函数声明,fun返回值类型为数组指针,数组长度为5
    
    typedef :定义类型别名
    typedef int arrayT[5];	//自定义类型别名,arrayT代表长度为5的int数组
    arrayT* fun2(int x);	//fun2的返回类型是指向arrayTd的指针
    
    c++11尾置返回类型
    auto fun3(int x) -> int(*)[5];
    
4.递归
  • 每次递归需要保存上下文

(七)函数高阶

1.内联函数
  • c++新增特性,省去函数调用,调用点代码直接替换,大大提高运行效率
  • 只需在函数声明或者函数定义前加上inline关键字
  • 在C中类似功能是通过预处理语句#define定义宏来实现
    • 宏本身并不是函数,无法进行值传递
    • 宏本质是文本替换,一般只用宏定义常量
    • 宏实现函数功能比较麻烦,可读性差,一般使用内联函数取代宏
2.默认实参
  • 定义默认实参,形式上就是给形参做初始化

  • 一旦某个形参被定义了默认实参,那它后面的所有参数都必须有默认实参,一般在末尾

  • 以传入实参为主

3.函数重载
(1)定义重载函数
  • 名字 相同但形参列表不同,这是C++相对于C语言的重大改进,也是面向对象的基础
  • 形参数量不同或类型不同
  • 主函数不能重载
(2)有const形参时的重载
  • 顶层const,常量作为形参,跟不加const完全等价

  • 底层const,引用传递,只能用常量引用来绑定常量,只能用常量指针来指向常量的地址

    • const限制了间接访问的数据对象是常量
    • 当实参是常量时,不能对不带const的引用进行初始化,只能调用常量引用做形参的函数
    • 如果实参是变量,就会优先匹配不带const的普通引用
(3)函数匹配
① 候选函数
  • 与调用函数同名
  • 函数的声明,在函数调用点是可见的
②可行函数
  • 形参个数与调用传入的实参数量相等,默认参数可以忽略
  • 每个实参的类型与对应形参的类型相同,或者可以转换成形参的类型
③寻找最佳匹配
  • 实参类型与形参类型越接近,他们就匹配的越好
④多参数的函数匹配
  • 逐个比较每个参数
  • 如果可行函数的所有形参都能够精确匹配实参,那么它就是最佳匹配
  • 如果没有全部精确匹配,那么当一个可行函数所有参数的匹配,都不比别的可行函数差、并且至少有一个参数要更优,那它就是最佳匹配
⑤二义性调用
  • 如果检查所有实参之后,有多个可行函数不分优劣、无法找到一个最佳匹配,那么编译器会报错,这被称为二义性调用
(4)重载与作用域
  • 如果内外层作用域都声明了同名函数,外层会被隐藏
  • 不同的作用域中,是无法重载函数名的
4.函数指针
(1)声明
  • 本质还是指针
  • 函数的类型是由它的返回类型和形参类型共同决定的
string(*fp)(string,int,double) = nullptr;
const string& (*fp1)(const string&,const string&)
(2)使用函数指针
  • 函数名后跟调用操作符(小括号)表示调用
  • 单独使用函数名作为一个值时,函数会自动转换成指针,跟数组名类似
  • 函数指针完全可以当做函数来使用
fp = &stuInfo;		//取地址符是可选地,和下面无区别
fp = stuInfo;		//直接将函数名作为指针赋给fp

(*fp)();			//fp做解引用可以得到函数
fp();				//解引用符是可选的
(3)函数指针作为形参
  • 如比大小传入比较函数,形参函数指针
const string& (*fp)(const string&,const string&)	//形参中*可省略,编译器自动补充,下面等价
const string& fp(const string&,const string&)
    
    
typedef const string& Func(const string&, const string&);	//自定义函数类型,Func
typedef const string& (*Funcp)(const string&, const string&);	//自定义函数指针类型,Funcp
  • C++11提供decltype函数直接获取类型,更加简洁
typedef decltype(longerStr) Func;
typedef decltype(longerStr)	*Funcp;
(4)函数指针作为返回值
  • 函数不能直接返回另一个函数,但是可以返回函数指针
  • 函数的返回类型必须是函数指针,而不能是函数类型
  • 可以采用c++11尾置返回类型
auto fun(int) -> Funcp;;

二、C++进阶

(一)内存分区模型

  • C++程序在执行时,将内存大方向划分为四个区域,赋予不同的生命周期
    • 代码区:存放函数体的二进制代码,由操作系统进行管理的
    • 全局区:存放全局变量和静态变量以及常量
    • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
    • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
1.程序运行前
  • 在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
    • 代码区:
      • 存放CPU执行的机器指令
      • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
      • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
    • 全局区
      • 全局变量和静态变量存放在此
      • 全局区还包含了常量区,字符串常量和其他常量也存放再次
      • 该区域的数据在程序结束后由操作系统释放
  • 全局区中存放全局变量、静态变量(static)、常量
  • 常量区中存放const修饰的全局常量和字符串常量
2.程序运行后
  • 栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
  • 堆区:由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
  • C++中主要利用new在堆区开辟内存
3.new操作符
  • 堆开辟的数据,利用操作符delete
  • new 数据类型
  • 利用new创建的数据,会返回该数据对应的类型的指针
  • deldete[] arr;释放堆区数组要加[]

(二)类和对象

  • C++面向对象的三大特性为:封装、继承、多态
1.封装
(1)封装的意义
  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制
    • public 公共权限
      • 成员内可以访问
      • 类外可以访问
    • protected 保护权限
      • 类内可以访问
      • 类外不可以访问
      • 子类可以访问父类中的保护内容
    • private 私有权限
      • 类内可以访问
      • 类外不可以访问
(2)struct和class区别
  • 唯一区别默认的访问权限不同
    • struct 默认权限为公共
    • class 默认权限为私有
(3)成员属性设为私有
  • 可以自己控制读写权限
  • 对于写权限,可以检测数据的有效性
2.对象的初始化和清理
(1)构造函数和析构函数
  • 编译器自动调用,如果不提供构造和析构,编译器会提供空实现的构造和析构,析构顺序:先进后出
  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值
    • 类名(){}
    • 没有返回值也不写void
    • 函数名称与类名相同
    • 可以有参数,因此可以重载
    • 无需手动调用,而且只会调用一次
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
    • ~类名(){}
    • 没有返回值也不写void
    • 函数名称与类名相同,在名称前加上符号~
    • 不可以有参数,因此不能重载
    • 无需手动调用,而且只会调用一次
(2)构造函数的分类及调用
  • 两种分类方式

    • 按参数分为:有参构造和无参构造(默认构造)
    • 按类型分为:普通构造和拷贝构造
      • 拷贝构造,传入常量引用
  • 三种调用方式

    • 括号法
    Person p1;		//默认构造函数调用
    Person p2(10);	//有参构造函数
    Person p3(p2);	/拷贝构造函数
    调用默认构造函数时候,不要加(),编译器会认为是一个函数的声明
    
    • 显示法
    Person p2 = Person(10);
    Person p3 = Person(p2);
    
    Person(10);	//匿名对象,当前行执行结束后,系统会立即回收掉匿名对象
    Person(p3);	//报错,不能利用拷贝构造函数初始化匿名对象编译器会认为Person(p3) === Person p3; 对象声明
    
    • 隐式调用法
    Person p4 = 10;	//相当于写了Person p4 = Person(10); 有参构造
    Person p5 = p4;	//拷贝构造
    
(3)拷贝构造函数调用时机
  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象,会返回新的对象
(4)构造函数调用规则
  • 默认情况下,c++编译器至少给一个类添加四个函数
    • 默认构造函数(无参,函数体为空)
    • 默认析构函数(无参,函数体为空)
    • 默认拷贝构造函数,对属性进行值拷贝
    • 赋值运算符operator=,对属性进行值拷贝
  • 构造函数调用规则如下:
    • 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造
    • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
(5)深拷贝与浅拷贝
  • 浅拷贝:简单的赋值拷贝操作
    • 指针,堆区的内存可能重复释放
  • 深拷贝:在堆区重新申请空间,进行拷贝操作
(6)初始化列表
  • Person(int a,int b,int c):m_A(a),m_B(b),n_C(c){
    }
    
(7)类对象作为类成员
  • 先构造类成员,再构造本身
  • 先析构本身,再析构类成员
(8)静态成员
  • 静态成员就是在成员变量和成员函数前加上static关键字,称为静态成员
  • 静态成员变量
    • 所有对象共享一份数据,不属于某个对象
      • 通过对象进行访问
      • 通过类名进行访问
      • 也有访问权限,类外不能访问私有
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享一个函数
      • 通过对象访问
      • 通过类名访问
      • 也有访问权限,类外不能访问私有
    • 静态成员函数只能访问静态成员变量
      • 不能确定是哪一个非静态
3.C++对象模型和this指针
(1)成员变量和成员函数分开存储
  • 在C++中,类内的成员变量和成员函数分开存储
  • 只有非静态成员变量才属于类的对象上
  • 空对象占用内存为1,C++编译器会给每个空对象分配一个字节空间,以区分空对象占内存的位置
(2)this指针概念
  • 每个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
  • this指针是隐含每一个非静态成员函数内的一种指针
  • this本质是指针常量
  • 指向被调用的成员函数所属的对象,链式编程思想
  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this
(3)空指针访问成员函数
  • c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

  • 如果用到this指针,需要加以判断保证代码的健壮性

    class{
    ...
    int age;
    void ShowPerson(){
    	if(this==NULL)
    		return;
    	cout <<"woshi"<<age<<endl;	//age默认省略,this->age
    }
    ...
    };
    
(4)const修饰成员函数
  • 常函数

    • 成员函数后加const
      • 修饰的是this指向,让指针指向的值也不可以修改
    • 常函数内不可以修改成员属性
    • 成员属性声明是加关键字mutable后,在常函数和常对象中依然可以修改
    mutable int m_B;
    void showPerson() const{
    	this->m_B=100
    }
    
  • 常对象

    • 声明对象前加const
    • 常对象只能调用常函数
4.友元
  • 关键字为 friend

  • 让一个函数或者类,访问另一个类中私有成员

  • 三种实现:

    • 全局函数做友元
    void gooGay(Building *building){}
    class Building{
    	friend void gooGay(Building *building);
    };
    
    • 类做友元
    class GoodGay{
    public:
    	GoodGay();
    	Building *buinding;
        void visit();
    };
    class Building{
    	friend class GoodGay;
    };
    
    //类外写成员函数
    GoodGay::GoodGay(){}
    
    • 成员函数做友元
    friend void GoodGay::visit();
    
5.运算符重载
  • 对已有的运算符重新定义,赋予其另一种功能,以适应不同到的数据类型

  • operator运算符替换函数名

    • p1+p2本质p1.operator+(p2)
    • 可以发生函数重载
  • 对于内置的数据类型的表达式的运算符是不可能改变的

(1)左移运算符重载
  • 可以输出自定义的数据类型
  • 通常不会利用成员函数重载<<运算符,因为无法实现cout在左侧,p.op<<cout
  • 只能用全局函数重载<<运算符
void operator<<(ostream& cout,Person&p){}	//本质cout<<p,不能链式
ostream& operator<<(ostream& cout,Person&p){
    return cout;
}
  • 重载左移运算符配合友元可以实现输出自定义数据类型
(2)递增运算符重载
  • 实现自己的整型数据
  • 前置++
MyInteger& operator++(){		//前置++,返回引用为了一直对同一个数据操作
    m_Num++;
    return *this;
}
  • 后置++
MyInteger operator++(int){		//int代表占位参数,可以用于区分前置和后置递增
	MyInteger temp = *this;
    m_Num++;
    return temp;
}
(3)赋值运算符重载
  • 编译器默认提供浅拷贝
Person& operator=(Person &p){
	if(m_Age!=NULL){
        delete m_Age;
        m_Age = NULL;
    }
    m_Age = new int(p->m_Age);
	return *this;					//链式编程
}
(4)关系运算符
  • 让两个自定义类型对象进行对比操作
bool operator==(Person& p){}
(5)函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
class MyAdd{
public:
    int operator()(int v1,int v2);
};
MyAdd myAdd;
myAdd(1,2);	//仿函数

MyAdd();		//匿名对象
MyAdd()(10,2);	//匿名函数对象,重载了小括号
6.继承
(1)继承的基本语法
  • 减少重复代码

  • class 子类 : 继承方式 父类

  • 子类、派生类

  • 父类、基类

(2)继承方式
  • 公共继承
  • 保护继承
  • 私有继承
(3)继承中的对象模型
  • 子类中所有非静态成员属性都会被子类继承下去,只是被编译器隐藏了访问不到
  • 利用开发人员命令提示工具查看对象模型:cl /d1 reportSingleClassLayout类名 文件名
(4)继承中构造和析构顺序
  • 先构造父类,再构造子类
  • 先析构子类,再析构父类
(5)继承同名成员处理方式
  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  • 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数,想访问需要加作用域
(6)继承同名静态成员处理方式
  • 与非静态一致
  • 通过类名访问
(7)多继承语法
  • C++允许一个类继承多个类
  • class 子类 : 继承方式 父类1, 继承方式 父类2,...
  • 多继承可能会引发父类中有同名成员出现,需要加作用域区分
  • 实际开发中不建议使用多继承
(8)菱形继承
  • 两个派生类继承同一个基类,又有某个类同时继承两个派生类,这种继承被称为菱形继承,或者钻石继承
  • 子类继承两份相同问题,导致资源浪费以及毫无意义
  • 继承之前加上virtual变为虚继承,Animal称为虚基类
  • 利用虚继承,解决菱形继承的问题
  • 继承两个指针,通过偏移量找到唯一数据
vbptr:
	v - virtual
    b - base
    ptr - pointer
该指针指向vbtable,记录偏移量        
7.多态
(1)多态的基本概念
  • 分为两类:

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

    • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
    • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
  • 重写:函数返回类型、函数名、参数列表完全相同

  • 动态多态满足条件:

    • 有继承关系
    • 子类重写父类的虚函数
  • 动态多态的使用:

    • 父类的指针或引用指向子类对象
  • 优点:代码组织结构清晰;可读性强;利于前期和后期的扩展以及维护

(2)多态的原理
  • 函数添加virtual后变成虚函数,类的结构发生改变,会添加一个指向 虚函数表vftable 的 虚函数(表)指针vfptr

  • 表内记录虚函数的地址,&Animal::speak

  • 当子类重写父类的虚函数时,子类中的 虚函数表内部 会替换成 子类的虚函数地址,&Cat::speak

  • 当父类的指针或者引用指向子类对象的时候,发生多态

(3)纯虚函数和抽象类
  • 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0 ;
  • 当类中有了纯虚函数,这个类也称为抽象类
  • 抽象类特点:
    • 无法实例化对象
    • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
(4)虚析构和纯虚析构
  • 问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
  • 解决方式:将父类中的析构函数改为虚析构或者纯虚析构
  • 共性:
    • 可以解决父类指针释放子类对象
    • 都需要具体的函数实现
  • 区别:
    • 如果是纯虚析构,该类属于抽象类,无法实例化对象
virtual ~类名(){}		//虚析构
virtual ~类名() = 0;	//纯虚析构
类名::~类名(){}			//纯虚析构需要定义也需要实现,析构类的时候会调用	

(三)文件操作

1.文本文件
(1)写文件
    1. 包含头文件#include <fstream>
    2. 创建流对象ofstream ofs;
    3. 打开文件ofs.open("文件路径",打开方式),可以省略该步直接在上一步传参
    4. 写数据ofs<<"写入的数据";
    5. 关闭文件ofs.close();
  • 文件的打开方式可以配合使用,利用|操作符
(2)读文件
  • ifstream

  • ifs.open,ifs.is_open()判断是否打开成功

  • ifs.close

  • 四种读数据方式

2.二进制文件
(1)写文件
  • 打开方式要指定为ios::binary
  • 主要利用流对象调用成员函数write
  • ofstream& write(const char* buffer,int len);
  • 字符指针buffer指向内存中一段存储空间,len是读写的字节数
(2)读文件
  • 打开方式要指定为ios::binary
  • 需要判断文件是否打开成功
  • 主要利用流对象调用成员函数read
  • ifstream& read(char* buffer,int len);
  • 字符指针buffer指向内存中一段存储空间,len是读写的字节数

三、C++提高

(一)模版

1.模板的概念
  • 建议通用的模具,大大提高复用性

  • 不可以直接使用,它只是一个框架

  • 泛型编程,主要利用技术就是模板

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

2.函数模板
(1)语法与注意事项
  • 建立一个通用函数,其函数返回值类型和参数类型可以不具体制定,用一个虚拟的类型来代表
template<typename T>        /  template<class T>
函数声明或定义

template	-声明创建模板
typename	-表明其后面的符号是一种数据类型,可以用class代替
T			-通用的数据类型,名称可以替换,通常为大写字母
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 注意事项:
    • 自动类型推导,必须推导出一致的数据类型T,才可以使用
    • 模版必须要确定出T的数据类型,才可以使用
(2)普通函数与函数模板的区别
  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模版调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换
(3)普通函数与函数模板的调用规则
  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板
  • 函数模板也可以发生重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板
  • 总结:既然提供了函数模板,就不要提供普通函数,否则容易出现二义性
(4)局限
  • 问题:数组、自定义类型
  • 解决:运算符重载、类型具体化
  • 利用具体化模板,可以解决自定义类型的通用化
  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
3.类模板
(1)语法
  • 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表
template<typename T,...>	/	  template<class T1,class T2,...>
类
(2)类模板与函数模板区别
  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数
(3)类模板中成员函数创建时机
  • 类模板中的成员函数和普通类中成员函数创建时机是有区别的
    • 普通类中的成员函数一开始就可以创建
    • 类模板中的成员函数在调用时才创建
(4)类模板对象做函数参数
  • 类模板实例化出的对象,向函数传参的方式

    • 指定传入的类型 ——直接显示对象的数据类型(最常用)
    void printPerson1(Person<string,int>&p){}
    
    • 参数模板化——将对象中的参数变为模板进行传递
    template<class T1,class T2>
    void printPerson2(Person<T1,T2>&p){}
    
    • 整个类模板化——将这个对象类型模板化进行传递
    template<class T>
    void printPerson3(T &p){}
    
(5)类模板与继承
  • 当子类继承的父类是一个类模板是,子类在声明的时候,要指定出父类中的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模板
(6)类模板成员函数类外实现
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age){}
(7)类模板分文件编写
  • 问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
  • 解决:
    • 直接包含.cpp源文件
    • 将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
(8)类模板与友元
  • 全局函数类内实现——直接在类内声明友元即可
  • 全局函数类外实现——需要提前让编译器知道全局函数的存在
template<class T1, class T2>
class Person;
	
template<class T1, class T2>
void printPerson2(Person<T1,T2> p){}		//类外实现,提前知道函数存在,还需要提前知道类存在

template<class T1, class T2>
class Person{
	friend void printPerson(Person<T1,T2> p){}	//全局函数,类内实现
	
    friend void printPerson2<>(Person<T1,T2> p);	//全局函数,类外实现,不加<>是普通函数
}

四、C++11新特性

(一)右值引用

1.右值和右值引用
  • 左值——可取地址
  • 右值——不可取地址
  • 右值分为
    • 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等
    • 将亡值:与右值引用相关的表达式,比如,T&&类型函数的返回值,std:move的返回值等
//左值
int num = 9;
//左值引用
int& a = num;
//右值引用
int&& b = 8;
//常量左值引用
const int& c = num;
const int& c = b;
const int& c = d;	//右值引用给左值引用初始化
//常量右值引用
const int&& d = 6;

const int&& e = b;	//错误
int&& f = b;	//错误,右值引用只能由右值初始化

2.移动构造函数
  • 复用其他对象中的资源(堆内存),并非所有资源
Test(Test&& a):m_num(a.m_num){
	a.m_num = nullptr;	//防止a析构时把内存也析构
}
Test getObj(){
    Test t;
    return t;			//可以取地址
}
Test t = getObj();		//右侧返回的是临时量,才会调用移动构造
Test&& t1 = getObj();	//如果没有移动构造,调用拷贝构造。

return Test();		//不可以取地址
//没有移动构造函数,右侧返回临时匿名量,此方法复用所有资源
//函数返回右值引用相当于一个右值(将亡值),可以给右值引用初始化
3.&&的特性
  • 未定的引用类型:
    • 模版参数T&&
    • 自动类型推导auto &&
  • 注意const T&&表示一个右值引用,不是未定引用类型
  • 引用折叠:
    • 通过右值推导T&&或者auto&&得到的是一个右值引用类型
    • 通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导T&&auto&&得到的是一个左值引用类型
  • 右值传递,具名化变为左值
  • 左值和右值是独立于他们的类型的,右值引用类型被推导或传递后可能是左值也可能是右值
  • 编译器会将已命名的右值引用视为左值,将未命名的右值引用视为右值
4.move
  • 将左值转换为右值(将亡值)
  • 和移动构造类似,只是转移状态或所有权,没有内存拷贝
5.forward
  • 完美转发std::forward<T>(t);
    • 当T为左值引用类型时,t将被转换为T类型的左值
    • 当T不是左值引用类型时,t将被转换为T类型的右值

标签:const,函数,int,笔记,C++,类型,模板,指针
From: https://blog.csdn.net/xxs3218822676_/article/details/139584093

相关文章

  • 【安装笔记-20240608-Linux-动态域名更新服务之YDNS】
    安装笔记-系列文章目录安装笔记-20240608-Linux-动态域名更新服务之YDNS文章目录安装笔记-系列文章目录安装笔记-20240608-Linux-动态域名更新服务之YDNS前言一、软件介绍名称:YDNS主页官方介绍二、安装步骤测试版本:openwrt-23.05.3-x86-64注册填写子域名激活邮箱更......
  • c++ stringstream
    转载:https://blog.csdn.net/jllongbell/article/details/79092891v前言:  以前没有接触过stringstream这个类的时候,常用的字符串和数字转换函数就是sscanf和sprintf函数。开始的时候就觉得这两个函数应经很叼了,但是毕竟是属于c的。c++中引入了流的概念,通过流来实现字......
  • 【YOLOv8改进】HAT(Hybrid Attention Transformer,)混合注意力机制 (论文笔记+引入代
    YOLO目标检测创新改进与实战案例专栏专栏目录:YOLO有效改进系列及项目实战目录包含卷积,主干注意力,检测头等创新机制以及各种目标检测分割项目实战案例专栏链接:YOLO基础解析+创新改进+实战案例摘要基于Transformer的方法在低级视觉任务中表现出色,例如图像超分辨率。......
  • 【YOLOv8改进】EMA(Efficient Multi-Scale Attention):基于跨空间学习的高效多尺度注意力
    YOLO目标检测创新改进与实战案例专栏专栏目录:YOLO有效改进系列及项目实战目录包含卷积,主干注意力,检测头等创新机制以及各种目标检测分割项目实战案例专栏链接:YOLO基础解析+创新改进+实战案例摘要通道或空间注意力机制在许多计算机视觉任务中表现出显著的效果,可以......
  • 【YOLOv8改进】ACmix(Mixed Self-Attention and Convolution) (论文笔记+引入代码)
    YOLO目标检测创新改进与实战案例专栏专栏目录:YOLO有效改进系列及项目实战目录包含卷积,主干注意力,检测头等创新机制以及各种目标检测分割项目实战案例专栏链接:YOLO基础解析+创新改进+实战案例摘要卷积和自注意力是两个强大的表示学习技术,通常被认为是彼此独立的两......
  • SpringBoot 学习笔记
    表示层>业务层>持久层>数据库使用分层结构进行解耦表示层controller包用来存放表示层的各种动作类。命名规范:xxxController如何让一个类变为动作类:使用@RestControl注解packagecom.hello.controller;@RestController//让SpringBoot认识这个类是动作类pu......
  • Vue 学习笔记
    Vue.jsVue常用官网vue.jsv-charts(图表,地图)webpackvue-routeraxiosvue-cliElement-Plus白卷npminstall-gvue-cli#安装vue2脚手架vueinitwebpackwj-vue#初始化项目,一路回车npmrundev#启动项目VueCLI:Vue官方提供的脚手架工具npminstall@......
  • Java之数据库连接桥梁JDBC学习笔记
    JDBC调用Java与数据库的连接桥梁是JDBC(JavaDatabaseConnectivity)。JDBC是Java编程语言中用于连接和执行数据库操作的API(应用程序编程接口)。它提供了一种标准的方法,允许Java程序与各种数据库(如MySQL、PostgreSQL、Oracle、SQLServer等)进行交互。JDBC主要包括以下几个核......
  • 程序设计与算法(三)C++:第四章poj代码
    课程:北京大学程序设计与算法(三)   MOOCOJ:OpenJudge014:MyString这个题需要写的函数有点多我们来分析一下。charw1[200],w2[100]; while(cin>>w1>>w2){ MyStrings1(w1),s2=s1;//构造函数题目有了,不用写//复制构造函数没有,需要写 MyStrings3......
  • 《人月神话》阅读笔记
      《人月神话》是一本深具启发性和指导意义的经典著作,对软件工程领域的种种现象进行了深入的思考和分析。这本书在我看来,不仅是一部技术著作,更是一部关于团队管理、项目管理以及软件开发过程中普遍存在的问题的深刻剖析。在阅读过程中,我深刻体会到了布鲁克斯的洞察力和智慧,也......