时间,每天得到的都是24小时,可是一天的时间给勤勉的人带来智慧与力量,给懒散的人只能留下一片悔恨。 --鲁 迅
学习C++永无止境,往前冲吧少年!
TIP:本博客主要以《C++语言程序设计》知识点内容为主,以在Linux/UNIX环境下进行编程,以及在此基础上会补充一些相关知识。
C语言有很多优点,也有很多不足。C++诞生了!
为了使SGI STL的基本代码都适用于VC++和C++ Builder等多种编译器,俄国人Boris Fomitchev建立了一个free项目来开发STLport,此版本STL是开放源码的。(似乎是不再更新,并不支持C++11)
Extensions(函式库扩充)的一般名称。TR1是一份文件,内容提出了对C++标准函式库的追加项目。这些追加项目包括了正则表达式、智能指针、哈希表、随机数生成器等。TR1自己并非标准,他是一份草稿文件。然而他所提出的项目很有可能成为下次的官方标准。这份文件的目标在于「为扩充的C++标准函式库建立更为广泛的现成实作品」。
Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称。
Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容。在C++社区中影响甚大,是不折不扣的“准”标准库。C编程的基本策略是,用程序把源代码文件转换成可执行文件
预处理命令主要有三个部分:条件编译,文件引入,宏展开
编译指的是把源代码翻译成汇编指令的过程
把二进制文件转换成可链接文件,此过程中会把静态链接库加入其中
静态链接库和动态链接库的区别:
但是一般使用的时候都还是找不到动态库的位置!!!
在此先介绍一个命令echo:输出字符串或变量
查看一个程序链接库位置的命令:ldd
其实系统是有自动寻找链接库的位置的,一般是在
所以给这个动态链接库生成一个软链接(相当于一个快捷方式)
// 放置的都是绝对路径
// 给动态链接库设置库环境变量
但是这种方法是一次性的,关机就要重新设置了,不推荐
我们只需要给他加上绝对路径即可
GNU编译器集合和LLVM项目
GNU项目起始于1987年,是一个开发大量免费UNIX软件的集合:GNU‘s Not UNIX,也被称为GCC,他的C编译器紧跟C标准的改动,用gcc命令便可调用GCC C编译器,许多使用cc作为gcc别名。
LLVM项目成为cc的另一个替代品,他的Clang编译器处理C代码,可以通过调用clang调用
gcc和clang命令都可以根据不同的版本选择运行时选项来调用不同C标准
C++代码,还有两个库也要非常重视了,libc++/libstdc++,这两个库有关系吗?有。两个都是C++标准库。
那g++是做什么的? 慢慢说来,不要以为gcc只能编译C代码,g++只能编译c++代码。 后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。在编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,需要这样,gcc -lstdc++, 所以如果你的Makefile文件并没有手动加上libstdc++库,一般就会提示错误,要求你安装g++编译器了
gcc和g++标准库在计算机位置
C++17:
gcc7完全支持,gcc6和gcc5部分支持,gcc6支持度当然比gcc5高,gcc4及以下版本不支持。
C++14:
gcc5就可以完全支持,gcc4部分支持,gcc3及以下版本不支持。
C++11:
gcc4.8.1及以上可以完全支持。gcc4.3部分支持,gcc4.3以下版本不支持。
高版本的gcc向下兼容,支持低版本的C++标准。
VS全名是Microsoft Visual Studio目前已经出到2017了,是很大的一个开发环境,包含很多高级语言的开发环境,VC、VB等,VC只是VS其中的一个开发环境。
vc版本与vs版本对应关系如下所示:
命名空间可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
命名空间的定义使用关键字 namespace,后跟命名空间的名称,如下所示:
为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示: // 调用第一个命名空间中的函数 // 调用第二个命名空间中的函数
您可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。 // 调用第一个命名空间中的函数
给变量起别名,相当于typedef
当然,using引用基类不止这么简单,基类中的size()是public,派生类继承的方式是private,所以size()也是private,想让他变成public,直接用using用法
命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。
所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:
定义宏,来在预处理阶段的时候用替换体替换宏
与ifdef类似,通常用语防止多次包含一个文件
让预处理器发出一条错误消息
重置LINE 和 FILE宏报告的行号和列号
告诉编译器自己要怎么做,直接把编译器指令放到源代码中,例如
其他可见pragma预处理命令
占用字节数根据具体实际情况而定
有符号整型是常规的数值表示范围,无符号是什么意思呢?
// 其实就是把负数部分的数值范围加到正数部分整数19不同进制的表示 | 整数-19不同进制的表示 |
---|---|
由0~9的数字序列组成,以10为基的数字系统,可以转化成二进制,八进制和十六进制 | |
由0,1的数字序列组成,以2为基的数字系统,可以转化成十进制,八进制和十进制 | |
由0~7的数字序列组成,以8为基的数字系统,可以转化成二进制,十进制和十六进制 | |
由0到9,A到F(或a到f)的序列组成,以16为基的数字系统,可以转化成二进制,八进制和十进制 | |
为什么计算机中浮点数是这样表示的呢?相信大家都会有这个疑惑
我们知道数值是计算机分成定点数和浮点数,浮点数的定义为:小数点的位置不固定,由指数,基数和阶层组成,用科学计数法表示。比如2 x 10^4
浮点数在计算机中用IEEE754二进制浮点数算术标准
其中:移码 = 阶码真值 + 偏置值(此处偏置值为2 ^ (n-1)-1)= 移码 - 偏置值
在IEE7554标准中,用真值+偏置值,在-127,-128会变成全0或者全1,因此真值的正常范围是-126~127
TIP:对于单精度浮点数是有一个隐藏的最高位1,在小数点前显示,如0.11 = 1.1 * 2^(-1)
在计算机刚发明的时候,字符使用的是ASCII码,表示只能是英文
而随着其他语言的加入,26个字母显然不太符合,因此根据中文,Unicode码便出现了。
而对应的,一个字符占用一个字节的情况变成了一个字符占用两个字节
// 末尾自动添加一个'\0',占4个字节 // 占用10个字节,一个中文2个字节,末尾无'\0',不合法 // 占用12个字节,一个字符两个字节,末尾添加一个'\0'ASCII码值(十进制) |
---|
退格(BS) ,将当前位置移到前一列 |
换页(FF),将当前位置移到下页开头 |
换行(LF) ,将当前位置移到下一行开头 |
回车(CR) ,将当前位置移到本行开头 |
水平制表(HT) (跳到下一个TAB位置) |
代表一个反斜线字符''\' |
代表一个单引号(撇号)字符 |
1到3位八进制数所代表的任意字符 |
1到2位十六进制所代表的任意字符 |
int,它是两个指针相减的结果 |
C语言提供了很多有用的整数类型,但是,某些类型名在不同系统下的功能不一样,C99新增了两个头文件stdint.h和inttypes.h,以确保C语言的类型在各系统中的功能相同
后置递增:先赋值后递增
前置递增是直接先递增后赋值,而后置递增是先进行赋值,而后开辟了一个新的内存来存放新的增加的数据,因此对于内存占用而言,前置递增比后置递增更具高效。
提示:根据前面布尔类型可知,'0'代表false,'1'代表true
0 |
0 |
0 |
作用:用于根据表达式的值返回真值或假值
如果a为假,则!a为真;如果a为真,则!a为假。 |
如果a和b都为真,则结果为真,否则为假。 |
如果a和b有一个为真,则结果为真,二者都为假的时候,结果为假 |
按位取反(bitwise NOT)运算,也就是将每一位是1的变成0,是0的变成1。这一运算的运算符为~:
按位与(bitwise AND)运算,在每一位上对0和1进行逻辑与运算。这一运算的运算符为&:只有两个数的二进制同时为1,结果才为1,否则为0
按位或(bitwise OR)运算,在每一位上对0和1进行逻辑或运算。这一运算的运算符为|:参加运算的两个数只要两个数中的一个为1,结果就为1
a^b的位运算规则是:当且仅当被运算两位上数不同时该位运算结果为1,否则该位运算结果为0。
规则 1:char、short 和 unsigned short 值自动升级为 int 值。细心的读者可能已经注意到,char、short 和 unsigned short 都未出现在表 1 中,这是因为无论何时在数学表达式中使用这些数据类型的值,它们都将自动升级为 int 类型。 规则 2:当运算符使用不同数据类型的两个值时,较低排名的值将被升级为较高排名值的类型。 规则 3:当表达式的最终值分配给变量时,它将被转换为该变量的数据类型。 规则 4:当变量值的数据类型更改时,它不会影响变量本身C的强制类型转换是把变量从一种类型转换为另一种数据类型
static_cast静态转换相当于C语言中的强制转换,但不能实现普通指针数据(空指针除外)的强制转换,一般用于父类和子类指针、引用间的相互转换。
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。不管是否发生多态,父子之间互转时,编译器都不会报错。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的,但是编译器不会报错。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何指针类型转换成空指针类型。
⑤可以对普通数据的const和non_const进行转换,但不能对普通数据取地址后的指针进行const添加和消去。
⑥无继承关系的自定义类型,不可转换,不支持类间交叉转换。
动态转换的类型和操作数必须是完整类类型或空指针、空引用,说人话就是说,只能用于类间转换,支持类间交叉转换,不能操作普通数据。
主要用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换,
①进行上行转换(把派生类的指针或引用转换成基类表示)是安全的,允许转换;
②进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的,不允许转化,编译器会报错;
③发生多态时,允许互相转换。
④无继承关系的类之间也可以相互转换,类之间的交叉转换。
⑤如果dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常
const_cast,用于修改类型的const或volatile属性,不能对非指针或非引用的变量添加或移除const。
最鸡肋的转换函数,可以将任意类型转换为任意类型,因此非常不安全。只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式
前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节(可查看virtual虚函数博客详述),这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换
字符串是一个或多个字符的序列。每一个字符串都有一个\0字符,用他来标记字符串的结束,空字符不是数字0,其ASCII值是0,这意味着数组的容量必须是至少比待定字符串中的字符数多1.
编译程序时,程序中所有的NAME都会被替换成1,这一过程称为编译时替换,是在预处理的时候进行的。
C90标准新增了const关键字,用语限定一个变量可读
取出数据到输出设备中,遇到空格终止输出 |
---|
用于科学计数法表示浮点数 |
我也看不懂,只能看到他的返回值有int类型,因此用一用就知道了嘛
所以他输出的是printf的字符串的长度哦!
输入数据到缓冲区,跳过空格,制表符和换行符
用于科学计数法表示浮点数 |
他们每次只处理一个字符,读取任何字符
大部分系统在用户按下Enter键之前不会重复打印刚输入的字符,这种输入形式成为缓冲输入。用户输入的字符被收集并存储在一个被称为缓冲区(buffer)的临时存储区,按下Enter键之后,程序才可使用用户输入的字符
// C中定义缓冲区的大小为512个字节完全缓冲区:当缓冲区被填满时才被刷新缓冲区,通常出现在文件输入中。
行缓冲区:出现换行符的时候刷新缓冲区
fgets:C 库函数 char gets(char str) 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
fputs:C 库函数 int puts(const char *str) 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中。
C++依赖于C++的I/O解决方案,而不是C语言的I/O解决方案,前者是在头文件iostream和fstream中定义一组类
charT>是一个模板类,位字符类型定义了具体特性,如比较字符是否相等以及字符的EOF值等等
write():显示字符串
如果程序使用cout将字节发送给标准输出,由于缓冲,此时不会立即发送到目标地址,而是被存储在缓冲区中,直到缓冲区填满,然后,程序将刷新(flush)缓冲区,把内容发送出去,并情况缓冲区,以存储新的数据。
对于屏幕输出来说,程序不必等到缓冲区被填满。例如,将换行符发送到缓冲区后,将刷新缓冲区。另外,多数C++实现都会在输入即将发生时刷新缓冲区。
程序期待输入这一事实,将导致他立刻显示cout消息(即刷新PL;消息),即使输出字符串中没有换行符。如果没有这种特性,程序将等待输入,而无法通过cout消息来提示用户
如果实现不能在所希望时进行刷新输出,可以使用两个控制符中的一个来强行进行刷新:
TIP:ends插入一个空字符
可以使用width成员函数将长度不同的数字放到宽度相同的字段中
当调整字符宽度有多余的宽度时,默认是用空格填充,当然,我们自己可以指定
浮点数精度的函数取决于输出模式,在默认模式下,他指的是显示的总位数
setf()函数有两个原型
对于输出,使用C++基数前缀 |
对于16进制输出,使用大写字母,E表示法 |
如果到达文件尾,则设置为1 |
如果流被破坏,则设置为1;例如,文件读取错误 |
如果输入操作未能读取预期的字符或输出操作没有写入预期的字符,则设置为 1 |
如果流可以使用,则返回为true |
返回一个位掩码,指出哪些标记导致异常被引发 |
读取下一个字符,即使该字符是空格、制表符或换行符
还读取空白,但使用返回值来将输入传递给程序
字符传入时函数的返回值 |
达到文件尾函数的返回值 |
getline()和get()区别:get()读取换行符,getline不读取换行符
分类:顺序结构,选择结构,循环结构
作用:执行满足条件的语句
单行格式if语句:if(条件){条件满足执行的语句}; 多行格式if语句:if(条件){条件满足执行的语句}else{条件不满足执行的语句}; 多条件的if语句:if(条件1){条件1满足执行的语句}else if(条件2){条件2满足执行的语句}...else{都不满足执行的语句}作用:执行多条件分支语句
作用:满足循环条件,执行循环语句
解释:只要循环条件的结果为真,就执行循环语句
作用:满足循环条件,执行循环语句
carefully:与while的区别在于do...while会先执行一次循环语句,再判断循环条件
作用:满足循环条件,执行循环语句
语法:for(起始表达式; 条件表达式; 末尾循环体){循环语句;}
作用:用于跳出选择结构或者循环结构
作用:在循环语句中,跳过本次循环中余下尚未执行的代码,继续执行下一次循环
作用:可以无条件跳转循环
解释:如果标记的名称存在,执行到goto语句,会跳转到标记的位置
数组是用一定顺序关系描述的若干对象集合体,组成数组的对象称为该数组的元素,数组元素用数组名与带括号的下标表示,同一数组的各元素具有相同的数据类型。
内存上数组是一串连续的空间
可知,数组名表示的是数组首元素的地址
// 空缺的部分用\0代替 获取数组长度:(1). 数据类型是字符串:strlen(); // 数组名即首元素地址 // 获得第二个元素的地址 // 获得第二个元素的值
作用:将一段经常使用的代码封装起来,减少重复代码,一个较大的程序,一般分为若干个程序块,每个模块实现特有的功能
函数的定义:返回值类型;函数名;参数表列;函数体语句;return表达式
TIP:如果只是单纯的函数定义,是不占任何内存的,只有当函数调用的时候才会占用内存
返回值类型 函数名 (参数列表){
功能:使用定义好的函数
TIP:函数调用的时候,会将函数以及函数的参数和函数的返回值进入栈区,占用内存。
函数的常见样式:无参无返,无参有返,有参无返,有参有返
函数的参数类型不同,或参数个数不同,或二者兼得,这叫做函数重载
函数调用的时候,会消耗很大的内存,因此为了解决这一矛盾,出现了内联函数,通过将函数体内的代码直接插入函数调用处来节省调用函数的时间开销,以空间换时间的方案,目的是提高函数的执行效率。
不可含有if、switch语句或循环语句
函数体不宜过大,一般不超过5行
Lambda表达式(也叫lambda函数,或简称lambda),是从C++ 11开始引入并不断完善的,是能够捕获作用域中变量的匿名函数对象。
捕获列表是零或多个捕获符的逗号分隔符列表,可选地以默认捕获符开始(仅有的默认捕获符是 & 和 = )。默认情况下,从lambda生成的类都包含一个对应该lambda所捕获变量的数据成员。类似任何普通类地数据成员,lambda的数据成员也在lambda对象创建时被初始化。类似参数传递,变量的捕获方式也可以是值或引用。 //将i拷贝到明位f的可调用对象 //对象f包含i的引用
[ ]。空捕获列表,lambda不能使用所在函数中的变量。
[=]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
[&]。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
[this]。函数体内可以使用Lambda所在类中的成员变量。
[a]。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
[&a]。将a按引用进行传递。
[=,&a, &b]。除a和b按引用进行传递外,其他参数都按值进行传递。
[&, a, b]。除a和b按值进行传递外,其他参数都按引用进行传递。
lambda形参列表和一般的函数形参列表类似,但不允许默认实参(C++14 前)。当以 auto 为形参类型时,该 lambda 为泛型 lambda(C++14 起)。与一个普通函数调用类似,调用一个lambda时给定的实参被用来初始化lambda的形参。
当我们需要为一个lambda定义返回类型时,需要使用尾置返回类型。返回类型若缺省,则根据函数体中的 return 语句进行推断(如果有多条return语句,需要保证类型一直,否则编译器无法自动推断)。默认情况下,如果一个lambda函数体不包含return语句,则编译器假定返回void。
略,同普通函数的函数体。
mutable:允许 函数体 修改各个复制捕获的对象,以及调用其非 const 成员函数;
constexpr:显式指定函数调用运算符为 constexpr 函数。此说明符不存在时,若函数调用运算符恰好满足针对 constexpr 函数的所有要求,则它也会是 constexpr; (C++17 起)
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。假如我们希望能改变一个被捕获的变量的值,就必须在参数列表后面加上关键字mutable。而一个引用捕获的变量则不受此限制。
//加上mutable才可以在lambda函数中改变捕获的变量值
定义:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
语法:struct 结构体名{结构体成员列表};
通过结构体创建变量方式有三种:
定义结构体时顺便创建变量
// 给s1属性赋值,通过访问结构体变量中的属性 定义结构体时顺便创建变量
作用:将自定义的结构体放入到数组中方便维护
// 给结构体数组的元素赋值
作用:通过指针访问结构体中的成员
语法:利用操作符->可以通过结构体访问结构体属性 // 创建学生结构体变量 // 通过指针指向结构体变量 // 通过指针访问结构体变量中的数据
作用:结构体中的成员可以是另外第一个结构体
例如:每个老师辅导一个学员,一个老师的结构体中,记录一个学生的结构体 // 结构体嵌套结构体
作用:将结构体作为参数向函数中传递
传递方式:值传递,地址传递 // 值传递,形参发生了改变不会影响实参 // 地址传递,形参发生了改变会影响实参
结构体中const的使用场景
作用:用const防止误操作 // 将形参改成指针,可以减少内存空间,而且不会复制出新的副本出来
结构体数据顺序对内存影响
枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读
enum 枚举名 {枚举元素1,枚举元素2,……};
TIP:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:
结构体占用内存:按照结构体内存偏移量原则,最大字节对齐原则
枚举类型占用内存:默认为int类型,枚举类型的尺寸不能够超过int类型的尺寸
共用体占用内存:占用内存最大的元素对齐原则,union的大小为最长类型的整数倍
即其大小必须满足两个条件:
大小能被其包含的所有基本数据类型的大小所整除
作用:可以通过指针间接访问内存,提高代码运行的效率。
语法:数据类型 * 变量名;
// 2. 指针定义的语法:数据类型 * 指针变量名 // 3. 让指针记录变量a的地址 // 可以通过解引用的方式来找到指针指向的内存 // 指针前加一个*代表解引用找到数据并修改内存
// 判断一个数是否是回文数 如果指针p1指向数组a里面的第二个元素a[1],指针p2指向数组a里面的第五个元素a[4],那么p1 - p2 = 3(表示的是两指针所指变量之间相距的同类型变量的个数) 指针没有相加运算,没有任何意义
指针变量指针内存中编号为0的空间;
用途:初始化指针变量;
注意:空指针指向的内存是不可以访问的; // 指针变量指针内存中编号为0的空间 // 内存编号0~255位系统所占内存,不允许用户访问
指针变量指向非法的内存空间 // 指针变量p指向内存地址中编号为0x1100的空间
const修饰指针:常量指针;
const修饰常量:指针常量;
const即修饰指针,又修饰常量; // const修饰的是指针,指针指向可以改,指针指向的值不可以更改 // const修饰的是常量,指针指向不可以改,指针指向的值可以更改 // const修饰的是常量,指针指向不可以改,指针指向的值可以更改
概念:C++语言中的指针和数组有着密切的关系,数组名可以认为是一个常量指针
作用:利用指针访问数组元素 // 指向数组的指针,此时arr代表的是数组的首地址,数组名可以认为是一个常量指针,因此可以直接写成int * p = arr; p++; // 让指针向后偏移4个字节 int *p = a[0]; // p是一个执行整型变量的指针,他可以指向一般的整型变量,也可以指向数组中的元素
Hello是一个字面量,str指向的是一个地址,即
作用:利用指针做函数参数,可以修改实参的值
C98推出,这个是被摒弃的,为何?
C11引入,旨在替代不安全的 auto_ptr,它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象
其中p2接管了string对象的所有权后,p1的所有权被剥夺,可是两个指针会同时删除一份数据
基于引用计数的智能指针,如果程序要使用多个指针指向同一对象的指针,应该使用shared_ptr。
另一个特点是如果可以安全的放入STL容器中。
语法:数据类型 &别名 = 原名
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
// 返回局部变量引用,不可用
// 返回静态变量引用
int& test02() { // int& 函数名表示以引用的方式返回,其他如int* 函数名表示以指针的方式返回
引用的本质:在C++内部实现一个指针常量
作用:常量引用主要用来修饰形参,防止误操作
在函数列表中,可以加const修饰形参,防止形参改变实参
// 引用使用的场景,通常用来修饰形参
// 使用场景:用来修饰形参,防止误操作
// 加上const后编译器将代码修改
指针初始化可以指向null
引用初始值必须指向一个值
C++程序在执行时,将内存大方向划分为4个区域
代码区:存放函数的二进制文件,由操作系统进行管理的;
全局区:存放全局变量和静态变量以及常量;
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等;
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统完成;
意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
在程序编译后,生成了exe可执行文件,未执行该程序前分成两个区域
存放CPU执行的机器指令;
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可;
代码区是只读的,使其只读的原因是防止程序意外的修改了程序;
全局变量和静态变量存放于此;
全局区还包括了常量区,字符串常量和其他常量也存放于此;
该区域的数据在程序结束后由操作系统释放;
由编译器自动分配释放,存放函数的参数值,局部变量等;
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放;
由程序员分配释放,若程序员不释放,程序结束的时由操作系统回收;
在C++中主要利用new在栈区开辟内存
// 利用new关键字将数据开辟到堆区
// 分配100个int类型的数字指针
calloc丞数申请的内存空间是经过初始化的,全部被设成了e,而不是像malloc所申请的空间那样都是未经初始化的。
calloc函数适合为数组申请空间,我们可以将第二个参数设置为数组元素的空间大小,将第-个参数设置为数组的元素数量。
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型;
利用new创建的数据,会返回该数对应的类型的指针 // 在堆区创建整型数据 // new返回是 该数据类型的指针 // 堆区的数据,程序员手动开辟,手动释放 // 在堆区利用new开辟数组 // 创建10整型数据的数组,在堆区; // 释放数组的时候加上[]才可以
此时的new就是所谓的new operator。这个操作符是语言内建的,他的动作分成两部分
我们可以改变的是用来容纳对象的那块内存的分配行为。new operator调用某个函数,执行必要的内存分配动作。
其返回值类型是void*,返回一个指针,指向一块内存,未设初值的内存,size_t参数表示需要分配多少内存(size_t是sizeof的返回值)。如下
// 取得原始内存,用来放置一个string对象
// 让ps指向新完成的对象
正常情况下,对一个已存在的对象调用constructor没有意义,因为constructor用来将对象初始化,而对象只能被初始化一次。
在某种情况下,偶尔会有一些分配好的内存,需要在上面构建对象,有一个特殊版本的operator new 成为placement new允许这么做。
函数返回指针,指向一个widget object,他被构造于传递给此函数的一块内存缓冲区上。因为这块内存是在函数外声明的,因为当函数释放的时候该内存不会被释放
这便是placement new。operator new的目的在于为对象找到一块内存,然后返回一个指针指向他,在placement new的情况下,调用者已经知道指向内存的指针了,因为调用者知道对象应该放在那里。
具体可见头文件new.h
理解I/O和内核的关系
当运行一个程序(软件)的时候,不免要和硬件进行对接协议,这样一个一个对接太麻烦了,因此就产生了内核
这样就软件就可以和内核对接,然后内核再把这些分发给硬件处理协议
内核主要进行内存管理,进程管理,虚拟文件系统,设备光驱
内核具有的权限很大,应用程序具有的权限很小,因此计算机把内存分为了内核态和用户态
他们之间的关系是这样的:
内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。
而在进程PCB管理可以看做是一个结构体,里面有一个文件操作符表,表长为1024,从0开始,其中前三个是固定的
当进行cout的时候,系统会产生一个FILE* stdout文件指针,该指针包括三部分:文件描述符,文件定位符以及缓冲区.其中的文件描述符是用来表示哪个文件的,会寄存到PCB的文件描述符表中去。
然后系统向下调用write函数(此时还是用户态)
接着再向下进行调用sys_write(此时进入了内核态)
前面说过,内核是硬件和应用程序之间的桥梁,因此,此时进入硬件:光驱,屏幕,网卡。此时进入的是屏幕
文件通常是在磁盘或固态硬盘上的一段已命名的存储区,C提供了两种文件模式:文本模式和二进制模式
除了选择问及那的模式,大多数情况下,还可以选择I/O的两个级别,底层I/O和标准高级I/O
C程序会自动打开3个文件,他们被称为标准输入、标准输出、标准错误输出。
默认打开三个进程,即在文件描述符中会占用0、1、2这三个文件描述符,因此,这三个文件描述符不可用
getc:表示从fp指定的文件中获取一个字符
putc:把一个字符放入FILE指针fpout指定的文件中
关闭fp指定的文件,成功返回0,否则返回EOF
/* 打开用于读取的文件 */
把字符 char(一个无符号字符)推入到指定的流 stream 中,以便它是下一个被读取到的字符。
C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。
程序运行时产生的数据都属于临时数据,程序一旦结束都会被释放,通过文件可以将数据持久化
文本文件:文件以文件的ASCII码形式存储在计算机中
二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读取他们
打开文件用于读取数据。如果文件不存在,则打开出错。 |
打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。 |
打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。 |
打开一个已有的文件,并将文件读指针指向文件末尾(读写指 的概念后面解释)。如果文件不存在,则打开出错。 |
打开文件时会清空内部存储的所有数据,单独使用时与 ios::out 相同。 |
以二进制方式打开文件。若不指定此模式,则以文本模式打开。 |
打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 |
打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 |
打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。 |
注意:文件打开方式可以配合使用,利用|操作符
// 3. 打开文件并判断文件是否打开成功
二进制方式写文件主要利用流对象调用成员函数write
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数。
二进制方式读文件主要利用流对象调用成员函数read
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
exit()函数结束程序,返回一个值给操作系统,告知程序的最后状态。在调用exit()函数之后,控制权会移交给操作系统。 在结束程序之前,exit()函数会调用之前使用atexit()注册过的所有函数,按照LIFO次序调用,关闭所有打开的文件,删除tmpfile()函数建立的所有临时文件。
abort()函数会发出一个SIGABRT信号来终止程序的执行。不会调用之前用atexit()函数注册的清理函数。至于会不会清除缓冲区,这个与系统相关。如以下实现则清空了。但没有关闭。进程结束它自己会关闭。
// 要监测的代码段,其中必须有抛出一个错误选择的异常
C++通常是将函数信息放在栈中的,当被调用的函数执行完毕后,程序将使用该地址来确定从哪里开始继续执行程序,另外,函数调用将函数参数放在栈中,在栈中,这些变量被视为自动变量,当一个函数中调用了其他函数,以此类推,当函数结束时,程序流程将调到该函数被调用时的存储的地址处,同时栈顶元素被释放,以此类推,每个函数都在释放其自动变量,如果自动变量是类对象,则类的析构函数将被调用。
该头文件定义了几个异常类,首先,logic_error和runtime_error类,他们都是以公有方式从exception派生而来的
// 每个类都有一个类似于logic_error的构造函数,提供一个供方法what()返回的字符串
该异常是所有标准 C++ 异常的父类。 |
---|
该异常可以通过 new 抛出。 |
这在处理 C++ 程序中无法预期的异常时非常有用。 |
该异常可以通过 typeid 抛出。 |
理论上可以通过读取代码来检测到的异常。 |
当使用了一个无效的数学域时,会抛出该异常。 |
当使用了无效的参数时,会抛出该异常。 |
当创建了太长的 std::string 时,会抛出该异常。 |
理论上不可以通过读取代码来检测到的异常。 |
当发生数学上溢时,会抛出该异常。 |
当尝试存储超出范围的值时,会抛出该异常。 |
当发生数学下溢时,会抛出该异常。 |
对于new而导致的内存分配问题,C++的最新处理方式是让new引发bad_alloc异常
如果内存不足的话则返回
很多代码都是在new在是失败时返回空指针时编码的,为处理new的变化,有些编译器提供了一个标记(开关),然后用户选择所需的行为
// 可将上一个例子的代码改为这些是动态异常规范,但是现在用的很少,我们现在一般都是直接定义为无异常抛出,以便编译器更好的执行代码
C++面向对象三大特性:封装,继承,多态
C++认为万事万物皆可为对象,对象上有其属性和行为
语法:class 类名{访问权限:属性 / 行为};
// 设计一个圆类,求圆的周长
// 通过圆类创建一个具体的圆
// 给圆对象的属性进行赋值
公共权限(public):成员类内可以访问,类外可以访问
保护权限(protected):成员类内可以访问,类外不可以访问,子类可以访问父类中的保护内容
私有权限(private):成员类内可以访问,类外不可以访问,子类可以不访问父类中的保护内容
在C++中唯一的区别就在于默认的访问权限不同
struct默认权限为公共;
class默认权限为私有;
生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除自己信息数据保证安全;
C++的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前清理数据的设置;
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后结果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作,对象的初始化和清理工作是编译器强制我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数:主要作用在于对象销毁前自动调用,执行一些清理工作
构造函数语法:类名(){}
析构函数语法:~类名(){}
// 构造函数:用于初始化类
这在类的单参数构造函数中表现的尤为明显!
构造函数一般有两种功能:构造函数和隐式类型转换
第二种情况下我们可以知道10是一个int整型变量,可是用“=”号就变成了
从类对象转换成int变量同理
所谓的隐式类型转换,是有一个奇怪的member function,关键词operator之后加上一个类型名称。
// 此处不能为此函数指定返回值类型,因为其返回值类型基本上已经表现欲函数名称上
可知在int d = 5 * r中可以将int作为一个运算符重载,因此此时类对象变成了一个int型变量
用explicit的意义
TIP:只能写在类声明中,不能写在类定义中
这种情况下就不允许进行这种int到类对象的隐式转换。(类的构造函数默认是implicit的隐式转换)
// 将传入的人身上所有属性(age)拷贝到我身上
C11最新引入,其实就是转移所有权
移动构造函数中的参数类型,&&符号表示是右值引用;即将消亡的值就是右值,函数返回的临时变量也是右值,这样的单个的这样的引用可以绑定到左值的,而这个引用它可以绑定到即将消亡的对象,绑定到右值.
// 1. 构造参数的分类和调用 /* 按照参数分类:无参构造(默认构造)和有参构造 按照类型分类:普通构造和拷贝构造 // 将传入的人身上所有属性拷贝到我身上
// 拷贝构造函数调用时机 // 1. 使用一个已经创建完毕的对象来初始化一个对象 // 2. 值传递的方式给函数参数传值 // 3. 以值方式返回局部对象
默认情况下,C++编译器至少给一个类添加3个函数
***后续完善:深拷贝和浅拷贝
作用:C++提供了初始化列表语法,用来初始化属性
// 传统的初始化操作 // 初始化列表操作初始化属性
C++类中中的成员可以是另一个类的对象,我们称该成员为对象成员
A a; // B类中有对象A作为成员,A为对象成员
m_A = 100; // 静态成员函数可以访问静态成员变量 // m_B = 200,报错,静态成员函数不可以访问非静态成员变量
那么静态成员函数有特点呢?
成员变量和成员函数是分开存储的,每一个非静态函数只会诞生一份函数实例,也就是说多个同类型的对象会公园一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
当形参和成员变量同名时,可以用this指针来区分;
在类的非静态成员函数中返回对象本身,可使用return *this // this指针指向被调用的成员函数所属的对象 // 当形参和成员变量同名时,可以用this指针来区分
- 成员函数加const后我们称这个函数为常函数;
- **常函数内不可以修改成员属性**;
- 成员属性声明加关键字mutable后,在常函数中依旧可以改变;
- 声明对象前加const称该对象为常对象;
- **常对象只能调用常函数**;
目的:让一个函数或者类访问另一个类中私有成员0
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
// 重载负运算符( - )
二元运算符需要两个参数,下面是二元运算符的实例。我们平常使用的加运算符( + )、减运算符( - )、乘运算符( * )和除运算符( / )都属于二元运算符。就像加(+)运算符。
下面的实例演示了如何重载加运算符( + )。类似地,您也可以尝试重载减运算符( - )和除运算符( / )。 // 重载 + 运算符,用于把两个 Box 对象相加 // 把两个对象相加,得到 Box3
您可以重载任何一个关系运算符,重载后的关系运算符可用于比较类的对象。
下面的实例演示了如何重载 < 运算符,类似地,您也可以尝试重载其他的关系运算符。 // 重载负运算符( - )
就像其他运算符一样,您可以重载赋值运算符( = ),用于创建一个对象,比如拷贝构造函数。
下面的实例演示了如何重载赋值运算符。
递增运算符( ++ )和递减运算符( -- )是 C++ 语言中两个重要的一元运算符。
下面的实例演示了如何重载递增运算符( ++ ),包括前缀和后缀两种用法。类似地,您也可以尝试重载递减运算符( -- )。 // 重载前缀递增运算符( ++ ) // 重载后缀递增运算符( ++ )
C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型。您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。
在这里,有一点很重要,我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。
下面的实例演示了如何重载提取运算符 >> 和插入运算符 <<。
函数调用()运算符重载
函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
下面的实例演示了如何重载函数调用运算符 ()。 // 重载函数调用运算符
继承啊面向对象三大特性之一
语法:class 子类(派生类):继承方式 父类(基类)
分类:公共继承,保护继承,私有继承
继承中的对象模型:子类继承父类的属性后在内存中依旧可以找到,说明是存在一种实际继承关系,而不是短暂的虚拟的继承关系。
继承中的构造和析构顺序
子类继承父类后,当创建子类对象时,也会调用父类的构造函数,此时是父类和子类的顺序是
父类构造-子类构造-子类析构-父类析构
// 继承中同名成员处理 // 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类的同名成员
继承同名静态成员处理方法
静态成员和非静态成员出现同名,处理方法一致
C++允许一个类继承多个类
class 子类:继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
多态是C++面向对象三大特性之一
// 地址早绑定 在编译阶段确定函数地址 // 如果想执行cat说话 地址近不能提前绑定 2. 子类重写父类的虚函数 父类的指针或者引用 执行子类对象 重写:函数有返回值类型 函数名 参数列表 完全一致称为重写
当子类发生重写时,子类中的虚函数表,内部会替换成子类的函数地址
当父类的指针或者引用指针子类对象的时候,发生多态
virtual tables:vtbls。一个由函数指针架构而成的数组(某些编译器会以链表取代数组)。程序中每一个class凡声明(或继承)虚函数者,都有自己的一个vtbl,而其中的条目就是该class的各个虚函数实现体的指针
注意,非虚函数f4并不在表格中,C1的constructor也一样
如果是C2继承C1,然后重新定义继承而来的虚函数
virtual table pointers:vptrs,凡是有虚函数的class,其对象都含有一个隐藏的data member,用来指向该class的vtbl。(必须在每一个拥有虚函数的对象付出一个额外指针的代价)
vptr主要有以下作用:
根据对象的vptr找出其vtbl,这是一个简单的动作,因为编译器知道对象的哪里去找出pvtr。成本只有一个便宜调整和一个指针间接动作
找出被调函数在vtbl内的对应指针,一个差移以求进入vtbl数组
调用上一个步骤所得指针所指向的函数
当发生继承的时候,派生类会把基类的虚函数指针vptr继承过来,不过此时指向变成了派生类的虚函数表
来看看一个特殊的多重继承:棱形继承
在多态中,通常父类中虚函数的是实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改成纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,因此需要将父类中的析构函数改成虚析构或者纯虚析构
虚析构和纯虚析构共性:
虚析构和纯虚析构区别:
// 父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
试读结束,下载后可查阅
[版权声明] 本站所有资料由用户提供并上传,若内容存在侵权,请联系邮箱。资料中的图片、字体、音乐等需版权方额外授权,请谨慎使用。网站中党政主题相关内容(国旗、国徽、党徽)仅限个人学习分享使用,禁止广告使用和商用。
开通VIP可收藏更多资料哦
未注册手机验证后自动登录
登录即代表您已阅读并同意 和
未注册用户验证后自动登录,登录即代表已阅读并同意与
2022年第24届冬季奥林匹克运动会将在北京举行,跳台滑雪是冬奥会的比赛项目之一。图为一简化后的跳台滑雪的轨道示意图,运动员(可视为质点)从起点由静止开始自由滑过一段圆心角为60°的光滑圆弧轨道后从A点水平飞出,然后落到斜坡上的B点。已知A点是斜坡的起点,光滑圆弧轨道半径为40m,斜坡与水平面的夹角θ=30°,运动员的质量m=60kg(重力加速度g=10m/s2 , 阻力忽略不计)。下列说法正确的是( )
A . 运动员到达A点时对轨道的压力大小为1200N B . 运动员从起点运动到B点的整个过程中机械能不守恒 C . 运动员到达A点时重力的瞬时功率为104W D . 运动员从A点飞出到落到B点所用的时间为
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。