├── ARM体系结构.c ├── ARM体系结构2.c ├── C++ - 副本.cpp ├── C++.cpp ├── C(Linux)知识点总结.c ├── Linux操作指令.c ├── Linux系统移植.c ├── Linux驱动.c ├── Makefile.c ├── Qt.cpp ├── README.md ├── UML.cpp ├── Unix_Linux - 副本.c ├── Unix_Linux.c ├── const_int_指针.c ├── gdb调试.c ├── io流 ├── 微信图片_20190821192215.png ├── 微信图片_20190821192254.png └── 微信图片_20190821192258.png ├── sort.c ├── tempCodeRunnerFile.c ├── 图.c ├── 多路复用IO.c ├── 排序.jpg ├── 数据库.sql ├── 数据结构.c ├── 新建 XLS 工作表.xls ├── 模板.cpp ├── 算法.c └── 通用链表和二叉树.c /ARM体系结构.c: -------------------------------------------------------------------------------- 1 | ARM体系结构-在裸板下的开发、单片机 2 | Linux系统移植-给开发板安装操作系统 3 | Linux系统驱动编程-对内核进行扩展 4 | 开发板 5 | SD卡 6 | 电源线 7 | 网线 8 | 串口线 9 | 蓝色USB线 10 | 11 | 一、嵌入式介绍 12 | 1、什么是嵌入式系统 13 | 嵌入到控件器的内部的软件,为特定的应用而设计的专用的计算机系统 14 | 以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。 15 | 16 | 学习嵌入式: 17 | 参与系统的裁剪 18 | 在此系统的基础开发应用 19 | 2、嵌入式系统的构成 20 | 软件 21 | 应用程序 22 | 功能库(Qt、madplay、libc、pthread) 23 | 文件系统 24 | 内核+驱动 25 | 引导程序 26 | 硬件 27 | 底板(硬件接口,网卡、声卡、显卡、外设) 28 | 核心板(SOC(CPU+内设),SDRAM,Nand 29 | 3、嵌入式系统的演进方向 30 | 1、以单芯片为核心,可以编程控制器存在的形式(单片机) 31 | 没有操作系统支持、软件以汇编为主,功能单一、处理效率低、存储器小、没有外部接口 32 | 2、以嵌入式CPU为基础、简单的操作系统为核心 33 | CPU种类多、通用性强、系统开销小、效率高、具有一定的拓展性和兼容性、用户体验度不高、交互不友好。 34 | 3、以嵌入式操作系统为标志 35 | 兼容性好、适用于不同平台、具有高度模块化及拓展性、界面化、用户交互友好 36 | 4、万物互联 37 | 将传感器技术、高速网络技术、人工智能、嵌入式技术综合联合在一起实现小型化、智能化、网络化、可视化、低功耗、绿色环保 38 | 配合异构多核技术、云计算、大数据、人工智能、虚拟化技术 39 | 4、嵌入式的开发模式 40 | 非嵌入式的开发:A类计算机编写源代码->编译得到可执行程序->发布给A类计算机执行。 41 | 嵌入式的开发:A类计算机编写源代码->编译得到可执行程序->发布给B类计算机执行。 42 | 为什么需要交叉编译:主要是嵌入式的CPU比较简单,本身无法安装开发工具,有的甚至没有操作系统,所谓的交叉编译就是用高性能的 43 | 计算机为低性能的计算机开发软件。 44 | 交叉编译的特点:必须使用专用的交叉编译工具,由于编译出的程序无法再本机执行,因此需要把程序通过特殊方法加载到目标设备上执 45 | 行(烧写)。 46 | 5、为什么需要SD卡等辅助设备 47 | 嵌入式设备所运行的软件(应用、库、文件系统、内核、驱动、引导程序)不会从本机上产生,它需要从其他机器上通过特殊复制SD卡上, 48 | 需要嵌入式设备从SD卡启动,来运行、调试相关软件。 49 | SD卡是嵌入式设备在没有安装好操作系统的情景下常用的一种调试方法。 50 | 网络接口是安装引导程序、操作系统后常用的一种调试方法,常用的网络调试工具有TFTP、FTP、NFS。 51 | USB也建议进行调试,但不稳定而且对开发的计算机有特殊要求,基本不用。 52 | 53 | 二、ARM处理器介绍 54 | 1、ARM的发展历史 55 | ARM的前身是艾康电脑,于1978年成立在英国剑桥。 56 | 在1980年左右,苹果电脑开始与艾康电脑合作开发新的ARM核心。 57 | 1995年推出全球第一款商用的RISC处理器。 58 | 精简指令集:只集成了一些基础运算的指令。 59 | 复杂指令集:它比精简指令集功能更强、指令更多,优点是使用汇编编程时更方便快捷,但随着高级语言的发展这个优势不复存在, 60 | 反而缺点功耗高、效率低、耗电更明显,对于嵌入式设备说这些缺点是致命的。 61 | 1990年艾康电脑财务危机,接受苹果的投资分割出独立的子公司ARM(Advanced RISC Machine),ARM公司正式成立。 62 | 从1991年开始推出第一款嵌入式精简指令集处理器架构。 63 | (时间不确定,仅供参考) 64 | 1993年,发布ARM7 65 | 1997年,发布ARM9 66 | 2001年,发布ARMv6 67 | 2002年,发布ARM11 68 | 2004年,发布ARMv7,同时分出三个系列是:Cortex-A Cortex-R Cortex-M 69 | 2005年,发布Cortex-A8 70 | 2007年,发布Cortex-A9 71 | 2010年,发布Cortex-A15 72 | 2011年,发布ARMv8 73 | 2012年,发布64位处理器 74 | 2、ARM的商业模式和生态系统 75 | ARM之前的半导体行业,Intel、ADM、Motorola等,自己设计IC、自己生成芯片、自己销售。 76 | 而ARM只负责设计IC,出卖自己的设计IP,自己不生产芯片,而是把IP授权给其他半导体厂商,来收取授权费。 77 | 因此ARM不是一家半导体厂商,而是一家设计公司。 78 | ARM的启示: 79 | 不要抱怨自己的劣势,劣势反而可能是机会。 80 | 要想成功,得先学会帮助别人、成全别人。 81 | 社会的成熟标志就是分工不断的细化。 82 | ARM发展的很快,也很慢。 83 | 3、为什么选择三星 84 | 本身使用广泛,有良好的企业应用基础。 85 | 资料多、积累好、方便学习。 86 | 开发板和方案商多,软硬件平台好找。 87 | 体系很典型,适用于学习。 88 | 4、ARM的版本号 89 | ARM型号命名问题 90 | 1、ARM7和ARMv7不是一回事 91 | 2、Cortex-A9比Cortex-A7更先推出 92 | 3、型号很混乱,没有规律 93 | ARM的几种型号 94 | 内核版本号 ARMv7 95 | Soc版本号 Cortex-A8 96 | 芯片型号 S5PV210 97 | 内核版本号和Soc版本号由ARM公司确定,芯片型号由半导体生产厂商确定。 98 | 99 | 三、开发板介绍 100 | 九鼎创展 x210V3s 目前已经停产,阅读硬件手册 101 | CPU:S5PV210 1GHz 102 | 内存:512MB/1GB DDR2 103 | 硬盘:4GB inand 104 | 串口:两路 RS232 接口,默认andorid2.3,WINCE使用uart0作为调试串口,android4.0和linux使用uart2作为调试串口,靠近电源接口。 105 | SD:2个SD卡接口,在使用 SD 卡启动时,一定要插到右侧的卡槽,否则无法从 SD 卡启动。 106 | LED:四路LED+1电源信号灯 107 | 复位按钮:关机,并没有重启的功能。 108 | 硬件开机开关:长按开机,9V直流电源 109 | 启动配置开关:拨到最上面是SD启动,最下面是USB,拨码开关靠近纽扣电池。 110 | 触摸屏:电阻、电容屏都支持 111 | WIFI:外接USB无线网卡 112 | 网卡:有线以太网 DM9000CEP 113 | 114 | 四、开发环境搭建 115 | 1、安装串口驱动 116 | 右击我的电脑->管理->设备管理器->串口,查看COM编号 117 | 2、安装串口通信工具 118 | 安装PuTTY->串口(COM编号,速度115200)->保存会话->双击会话。 119 | 长按开机按钮->显示字符即可。 120 | 3、安装SD卡烧写工具 121 | 以管理员身份运行x210_Fusing_Tool.exe,如果无法运行则安装vc2008运行库。 122 | 单击Browse按钮,选择要烧写的文件led_image.bin,单机Add按钮,单机START按钮,出现Fusing image done提示信息则表示烧成功。 123 | 注意:如果出现不可写的信息,问题1没有以管理员身份运行,问题2SD卡补上锁,否则就是SD卡损坏,请更换SD卡。 124 | 4、安装ARM交叉编译器 125 | 拷贝arm-2009q3.tar.bz2到共享目录,剪切到ubuntu主目录下,右击压缩包->解压到此处。 126 | 重命名为arm-linux-gnu,从终端进入arm-linux-gnu/bin,执行pwd命令,拷贝路径。 127 | 执行 sudo vi /etc/environment 打开环境变量配置文件,在PATH环境变量字符串的最前面添加路径,与后面的用':'分隔,然后保存 128 | 退出。 129 | 在终端中执行 source /etc/environment 130 | 执行arm-none-linux-gnueabi-gcc -v 可以查看交叉编译器的版本信息。 131 | 注意:安装交叉编译器时以厂商提供的为准。 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 为什么会用到汇编语言: 163 | C语言等高级语言的执行是需要一个环境的,比如:设置栈顶的指针 164 | 初始化一些硬件环境,比如:关闭看门狗、初始化DDR 165 | 对于性能有严苛要求的算法,比如:音视频解码器 166 | 167 | 一、ARM的体系结构 168 | 1、ARM处理器有7种工作模式 169 | 用户模式 执行正常的用户程序 170 | 系统模式 操作系统专用 171 | 快速中断 调速数据传输 172 | 外部中断 用于处理中断 173 | 特权模式 操作系统专用的保护域 174 | 数据访问中止 虚拟存储 175 | 未定义指令 遇到不支持的指令时进入此模式 176 | 分类: 177 | 用户模式和特权模式 178 | 正常模式和异常模式 179 | 2、ARM处理器的寄存器 180 | 一共37个寄存器,正常模式下能访问17个,异常模式下能访问18个。 181 | r0~r7 所有模式共用,也叫未分组寄存器,一旦模式切换就要立即备份。(但由于其通用性,在异常中断所引起的处理器模式切换时,其 182 | 使用的是相同的物理寄存器,所以很容易遭到破坏。 ) 183 | r8~r12 除快速中断外,其他模式共用 184 | r13、r14 正常模式共用,其他模式有自己私有的 185 | r15 程序计数器,它记录着处理器要执行的下一条指令,给它赋值可以达到跳转的效果,所有模式共用 186 | CPSR 当前程序状态寄存器,所有模式共用 187 | SPSR 备份程序状态寄存器,正常模式没有此寄存器,其他模式有自己私有的 188 | 189 | 二、流水线 190 | 通过合理安排硬件的分工来达到提高执行速度的效果,硬件本身的计算速度并没有提高。 191 | 互锁和跳转会影响流水线的速度。 192 | 193 | 三、ARM的寻址方式 194 | 立即寻址 ADD R0,R0,#1 <=> R0 += 1 195 | 寄存器寻址 add r0,r0,r1 <=> r0 += r1 196 | 寄存器间接寻址 ADD R0,R1,[R2] <=> R0 = R1 + *R2 197 | 寄存器偏移寻址 add R0,R0,R2,LSL #3 <=> R0 = R0 + R2 << 3 198 | 基址变址寻址 ADD R0,R0,[R1,#4] <=> R0 = R0 + *(R1+4) 199 | 多寄存器寻址 200 | LDMIA R0,{R1,R2,R3,R4} 201 | R1 = *(R0+0) 202 | R2 = *(R0+4) 203 | R3 = *(R0+8) 204 | R4 = *(R0+12) 205 | STMIA R0,{R3‐R5,R10} 206 | *(R0+0) = R3 207 | *(R0+4) = R4 208 | *(R0+8) = R5 209 | *(R0+12) = R10 210 | 堆栈寻址 211 | 满堆栈: 212 | 空堆栈: 213 | 214 | 215 | 四、ARM指令集 216 | 五、Thumb指令 217 | 六、ARM伪指令 -------------------------------------------------------------------------------- /ARM体系结构2.c: -------------------------------------------------------------------------------- 1 | 检查开发板: 2 | 1、网线 3 | 2、电源 4 | 3、USB转串口 5 | 4、SD卡 6 | 5、开发板 7 | 8 | 任务: 9 | 1、试用开发板 10 | 2、阅读用户手册 11 | 12 | 搭建开发板的开发环境: 13 | 1、安装USB转串口的驱动,安装完成后重启。 14 | 查看串口信息,设置编号,设置波特率。 15 | 右击此电脑->管理->设备管理器->串口->USB-to-Serial->右击属性->端口设置->设置波特率115200->高级->选择串口编号。 16 | 2、安装串口通信工具(putty、dnw) 17 | 以putty为例,选择串口,115200,会话名,保存,双击打开会话 18 | 此等待与开发板通信,长按电源键,如果不出错则有字符显示。 19 | 3、安装SD卡烧写工具 20 | 右击以管理员身份运行,如果无法运行,则安装vsc++2008运行库。 21 | 从image中下载led_image.bin到本机,然后把SD卡插入到电脑中。 22 | 点击Browse选择下载的led_image.bin,然后点击ADD,然后点击START,开始烧写。 23 | 把SD卡插入到开发板的靠近按键的卡槽中,然后长按电源键。 24 | 4、安装交叉编译器 25 | 复制arm-2009q3-tar.bz2到ubuntu系统,在主目录下解压。 26 | 在终端中进入arm-2009q3/bin目录下,使用pwd获取当前路径,然后复制。 27 | sudo vim /etc/environment,把刚才的路径粘贴到PATH的最前面,用:分隔,保存退出。 28 | 在终端执行 source /etc/environment 重新加载环境变量配置文件。 29 | 执行命令 arm-none-linux-gnueabi-gcc -v 查看交叉编译器的版本信息。 30 | 注意:64位的虚拟机安装下列工具 31 | sudo apt-get install lib32c-dev 32 | sudo apt-get install lib32stdc++6 33 | 34 | 开发板介绍: 35 | 1、x210v3s,由深圳九鼎创展生产,目前已经停产。 36 | 2、CPU由三星公司生产叫S5PV210,目前已经停产。 37 | 3、S5PV210采用ARMv7架构CortexTM-A8系列。 38 | 4、支持android4.0和android2.3,Qt4.8。 39 | 5、启动设置: 40 | 拨码开关在下:通过USB口启动, 41 | 拨码开关在上:是通过SD卡启动,优先内置的SD卡(硬盘),如果内置的SD卡,没有启动信息,则会从外置的SD卡启动。 42 | 6、串口: 43 | CPU带了四串口接口,但此开发板只外接了两个串口(UART0和UART2)。 44 | 注意,默认 andorid2.3,WINCE 使用 uart0 作为调试串口,android4.0 和 linux 使用 uart2 作为调试串口。 45 | 7、SD卡槽: 46 | 有两个SD卡槽,只有右侧可以使用。 47 | 8、LED和蜂鸣器: 48 | 一个蜂鸣器,有5个LED,其中一个电源指示灯。 49 | 用于调试程序。 50 | 9、电源: 51 | 此开发板采用了电源管理芯片,长按3秒才能开机。 52 | 如果没有电源锁定程序,松开后会立即断点。 53 | 54 | 什么是嵌入式: 55 | 是以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。 56 | 1、计算机设备,遵守冯·诺依曼体系结构。 57 | 1、运算器 58 | 2、控制器 59 | 3、存储设备(内存) 60 | 4、输入设备 61 | 5、输出设备 62 | 2、操作系统 63 | 因为嵌入式设备是一种专用性很强的计算机, 64 | 不是标准的操作系统,需要适当的进行裁剪(移植)。 65 | 3、嵌入式软件工程师 66 | 也是在操作的基础进行编程,调用系统接口、C/C++标准库、第三库(Qt/MySQL/Madplay)。 67 | 但要求更要,一般嵌入式设备的资源有限(存储设备、电量、运算速度)、环境恶劣。 68 | 4、嵌入式的开发模式 69 | 由于嵌入式的特殊性可能暂时没有操作系统,或者硬件的的速度慢无法运行开发工具,因此只能在PC机下写好代码,然后编译,下载、烧写的方式到嵌入式设备中运行。 70 | 普通的开发模板:A计算机开发,A计算机运行 71 | 嵌入式的开发模式:A计算机开发,编译出B计算机可以运行和程序,把可执行程序放在B计算机上运行,这种叫做交叉编译。 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 一、嵌入式系统的构成 98 | 软件: 99 | 应用程序 100 | 第三方库(Qt,libc,myclient) 101 | 操作系统: 102 | 引导程序 103 | 内核+驱动 104 | 文件系统 105 | 硬件: 106 | 底板: 107 | 外置芯片 108 | 网卡(DM9000) 109 | 声卡 110 | ADC 111 | 电源 112 | USB 113 | 接口: 114 | 串口 115 | SD 116 | LCD+触摸屏 117 | 摄像头 118 | 按键 119 | 核心板: 120 | Soc(CPU+uart+timer) 121 | DDR 122 | Nand 123 | 124 | 二、嵌入式系统的演进方式 125 | 1、以单芯片为核心、可编程控制器形式存在的 126 | 没有操作系统支持 127 | 软件以汇编为主 128 | 功能单一、处理效率低、存储器容量小、没有外部接口 129 | 使用简单、价格低 130 | 单片机 131 | 2、以嵌入式CPU为基础,简单操作系统为核心 132 | CPU种类多、通用性弱 133 | 系统开销小,效率高 134 | 具有一定的兼容性和扩展性 135 | 用户的体验不好,交互界面不够友好 136 | 3、以嵌入式操作系统为标志 137 | 兼容性好,适用于不同的平台 138 | 系统开销小、效率高、具有高度模块化及拓展性 139 | 图形界面、用户界面友好 140 | 141 | 4、以物联网为标志 142 | 以5G为基础,将传感器技术与传统的嵌入式技术相结合 143 | 小型化、智能化、网络化、可视化、低功耗、绿色环保 144 | 多核技术、云计算、大数据、人工智能相结构 145 | 146 | 三、ARM处理器介绍 147 | 1、发展历史 148 | ARM的前身是艾康电脑,于1978年成立于英国剑桥。 149 | 1980年苹果公司与艾康电脑合作开发新版的CPU,于1985年发布了全球第一款商用RISC处理器。 150 | 1990年艾康电脑财务危机,接受苹果公司的投资,分割出独立子公司ARM(Advanced RISC Machine)。 151 | 之后发布了一系统的RISC处理器设计方案。 152 | 2、商业模式和生态系统 153 | ARM之前的半导休行业 154 | Intel、ADM、Motorola这些公司都是自己设计IC,自己设计芯片、自己生产、自己销售。 155 | ARM的商业模式 156 | ARM只负责IC设计,销售自己的IP设计 157 | 自己不生产芯片,而是把IP设计授权其它的半导体生产商,收取专利费。 158 | 因此严格来说ARM不是半导休厂商,而是一家设计公司。 159 | ARM的启示 160 | 1、不要抱怨劣势,有时候反而可能是机会。 161 | 2、想成功,要先学习帮助别人,成全别人。 162 | 3、一个成熟社会的标志就是分工不断的细化。 163 | 3、为什么选择三星 164 | 1、在中国的使用广泛,有良好的企业应用基础。 165 | 2、学习资料多,便于学习。 166 | 3、开发板和方案多,软硬件平台好找。 167 | 4、体系典型,适用于学习。 168 | 169 | 四、ARM的体系结构 170 | 1、ARM的工作模式 171 | 用户模式 172 | 系统模式 173 | 快速中断模式 174 | 外部中断模式 175 | 特权模式 176 | 快速模式 177 | 未定义模式 178 | 除用户模式外,其它被称为特权模式 179 | 用户模式和系统模式外,其它被称为异常模式 180 | 2、ARM的寄存器 181 | 一共37个寄存器,用户模式和系统模式可以使用17个,其它模式可以使用18个(17+状态寄存器的备份)。 182 | r0~r7:所有模式共用 183 | r8~r12:快速中断模式有它私有的,其它模式共用。 184 | r13~r14:用户模式和系统模式共用,其它模式有私有的。 185 | r15、cpsr:所有模式共用 186 | r15也叫pc:程序计数器,它记录着下一条要执行的程序,可以被赋值,这样就实现了跳转。 187 | cpsr:用户来记录上一条指令的执行状态,进位、溢出、零、负数、当前模式,只有12位有效,其它位目前保留。 188 | spsr:用户模式和系统模式下没有,其它模式有私有的。 189 | 与cpsr的格式一样,它是用来备份用户模式和系统模式的cpsr。 190 | 191 | 五、流水线 192 | 一条指令的执行需要六个步骤: 193 | 1、取指 194 | 2、译码 195 | 3、取数 196 | 4、计算 197 | 5、存储 198 | 6、回写 199 | 如果只是按顺序执行,那么执行其中一项操作时其它硬件都处于空闲状态,因此ARM引入了流水线的概念,每个控制单元只负责干一件事情,这样理论上,三级流水线就提高了三倍的性能,而5级流水线就提高了五倍的性能。 200 | 但实际情况其实达不到,因为流水线会被打断、暂停。 201 | 比如: 202 | bl func //跳转指令 203 | ldr r0,[r0,#0] 204 | add r0,r0,r1 //在5级流水线上产生 205 | 206 | 六、ARM处理器寻址方式 207 | 1.立即寻址:#100 208 | 2.寄存器寻址:r0,把寄存器当变量使用 209 | 3.寄存器间接寻址:[r0],把寄存器当指针变量使用,*r0 210 | 4.寄存器偏移寻址:r0 << n,对寄存器进行左移或右移操作 211 | 5.基址变址寻址:[r0,#1],相当于对指针变量进行加减操作,*(r0+1); 212 | 6.多寄存器寻址:寄存器的批量操作,类似于数组初始化操作 213 | 7.堆栈寻址:在ARM汇编语句中也可以使用堆内存和栈内存,但前提是要设置好堆内存和栈内存的基地址,然后就可以向堆内存和栈内存读取、写入数据。 214 | 七、ARM指令集 215 | 1、ARM指令的格式 216 | {}{S} , ... 217 | opcode 指令码 218 | cond 条件码 219 | S 是否影响状态寄存器 220 | Rd 目标寄存器 221 | Rn 源寄存器 222 | {} 可以省略,而<>必须要有的 223 | 2、RM指令条件执行及标志位 224 | CMP会自动把比较结果存储到状态寄存器 225 | 而数据处理指令需要在指令反加S才会把计算结果存储到状态寄存器。 226 | EQ 相等 227 | NE 不等 228 | 3、跳转指令 229 | B 目标地址,是一种相对地址跳转,在当前地址的基础上加一个偏移值,进行跳转,这种跳转速度比较快,但是跳转的范围有限,正负32M以内。 230 | BL 目标地址,是一种绝对地址跳转,需要是完整的一个地址,在跳转前会把下一条指令的地址存储到r14中,然后跳转到目标位置执行,当执行完成后可以从r14中恢复到pc中,这样就实现了返回。 231 | BLX 功能与BL类似,但会从ARM(32位状态),切换到Thumb(16位状态)。 232 | BX 功能与B类似,但会从ARM(32位状态),切换到Thumb(16位状态)。 233 | 234 | 4、数据处理指令 235 | MOV r0,#100 <=> r0 = 100 236 | MOV R1,R3,LSL,#3 <=> r1 = r3 << 3 237 | MVN r0,#100 <=> r0 = ~100 238 | ADD r0,r1,#110 <=> r0=r1+110 239 | SUB R0,R0,#1 <=> r0-=1 反减 240 | RSB R3,R1,#0xFF00 <=> r3=0xff00-r1 241 | 242 | ADDS R1,R1,R2 243 | ADC R0,R0,R2 带进位的加 244 | 245 | SUBS R0,R0,R2 246 | SBC R1,R1,R3 带借位的减 247 | 248 | RSBS R2,R0,#0 249 | RSC R3,R1,#0 带借位的反减 250 | 251 | AND R0,R0,#3 <=> r0 = r0&3 252 | ORR R0,R0,#3 <=> r0 = r0|3 253 | EOR R1,R1,#3 <=> r1 = r1^3 254 | BIC R0,R0,#3 <=> r0 = r0&(~3) 255 | CMP R1,R0 把两个数的比较结果影响状态寄存器 256 | CMN R1,R0 把两个数求反后比较,并把比较结果结果影响状态寄存器 257 | TST R0,#0x01 把两个数进行按位与操作,并把计算结果影响状态寄存器 258 | TEQ R1,R2 把两个数进行按位异或操作,并把计算结果影响状态寄存器 259 | 5、程序状态寄存器传输指令 260 | MRS R7,CPSR 把当前模式的状态寄存器备份到r7 261 | MSR CPSR_cxsf,R3 把数据写入到状态寄存器 262 | [31:24] 为条件标志位域,用f表示 263 | [23:16] 为状态位域,用s表示 264 | [15:8] 为扩展位域,用x表示 265 | [7:0] 为控制位域,用c表示 266 | 6、Load、Store指令 267 | Load 从内存加载数据到寄存器 268 | LDR R0,#8 269 | LDR R0,[R1,#8] <=> r0 = *(r1+8); 270 | DMFD R13,{R0,R4‐R12,LR} 271 | 272 | Store 把寄存器中的数据写到内存 273 | STR #8,R0 274 | STR R0,[R1,#8] *(r1+8) = r0 275 | STMFD R13,{R0,R4‐R12,LR} 276 | SWP r0,r1,r2 <=> r0 = r2; r2 = r1; 277 | 7、中断指令 278 | SWI 0-16777215 一旦这条指令就会进入中断模式。 279 | 280 | 电源锁定: 281 | 1、找到厂家提供的代码,保存为start.S文件。 282 | start: 283 | ldr r0, =0xe010e81c 284 | ldr r1, [r0] 285 | orr r1, r1, #0x300 286 | orr r1, r1, #0x1 287 | str r1, [r0] 288 | 2、生成目标代码 289 | arm-none-linux-gnueabi-gcc -c start.S ->start.o 290 | 3、生成可执行文件(设置代码段、不加入启动代码、不加入标准库) 291 | arm-none-linux-gnueabi-gcc -Ttext 0xd0020010 -nostartfiles -nostdlib start.o -o lock 292 | 4、从可执行文件中拷贝出纯二进制指令 293 | arm-none-linux-gnueabi-objcopy -O binary lock lock.bin 294 | 5、编译出添加校验和的工具(研究一下,明天我们自己写一份) 295 | gcc mkv210_image.c -o mkv210 296 | 6、为lock.bin添加校验和 297 | ./mkv210 lock.bin lock_image.bin 298 | 7、把添加校验和后的文件烧写到SD卡中执行,效果:开发板能够持续供电。 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 一、常用的存储介质 325 | 内存: 326 | SRAM:静态随机存储器,容量小,但价格高,不需要初始化上电直接使用,在使用过程中不需要做一维护操作。 327 | DRAM:动态随机存储器,容量大,价格低,上电后不能直接使用,需要初始化,在使用过程中需要一些刷电操作,DDR,LDDR。 328 | IROM:ROM只读存储器,IROM用于存储指令的只读存储器,一般用于固化一些初始化程序。 329 | 外存: 330 | NorFlash:容量小,价格高,优点是可以与CPU的总线直接相连,CPU上电后可以直接读取,因此可以用作启动介质。 331 | NandFlash:(硬盘),容量大,价格低,缺点是不能总线式访问,也就是不能在CPU上电后直接使用,协议、时序复杂,一般都通过一个单独的控制器进行读写操作。 332 | 二、各大类计算机的启动方式 333 | PC:小容量的NorFlash+大容量的硬盘+大容量的DRAM 334 | 单片机:小容量的NorFlash+小容量的SRAM 335 | 嵌入式:大容量的DRAM+大容量的NandFlash+Soc内置少量的SRAM 336 | S5PV210:大容量的DRAM+外接大容量的NandFlash+Soc内置少量的SRAM+Soc内置少量的IROM。 337 | 三、S5PV210的启动顺序 338 | 通过S5PV210_iROM_ApplicationNote可以知道S5PV210的启动过程分为5大步骤: 339 | 1、BL0(从Nand拷贝小于96KB的程序到SRAM,然后跳转到BL1) 340 | 2、BL1(唤醒尝试闲置状态的SRAM,检查是是否是安全启动模式,然后跳转到BL2) 341 | 3、BL2(运行SRAM中的程序,初始化DRAM) 342 | 4、从Nand拷贝OS到DRAM 343 | 5、DRAM启动操作系统 344 | 三星在S5PV210中固化一段程序到iROM中,负责完成一些初始化工作。 345 | 1、关闭看门狗定时器 346 | 2、初始化高速缓存 347 | 3、初始化栈内存基地址 348 | 4、初始化堆内存基地址 349 | 5、初始化块设备拷贝函数 350 | 通过函数指针实现的回调模式。 351 | 调用代码已经实现,而具体的函数功能还没有实现出来。 352 | 6、初始化系统时钟 353 | 7、拷贝BL1程序到SRAM内存 354 | 8、检查前16个字节,BL1程序的校验和。 355 | 9、检查是否是安全启动模式 356 | 10、跳转到BL1位置执行 357 | 358 | 练习:实现计算校验和和功能。 359 | 1、准备16kb的缓冲区 360 | 2、缓冲区前16个字节,先定写入****************。 361 | 3、从文件中读取内容,读取缓冲区16个字节后。 362 | 4、把读取的内容,每个字节相加到一个8字节的整数,然后把它写入到8~15处。 363 | 5、把缓冲区里的所有内存到新文件中。 364 | 365 | 四、GPIO介绍 366 | GPIO即通用输入/输出接口,简称GPIO。 367 | 优点:低功耗、封装小、成本低、快速上市。 368 | 369 | 五、GPIO的寄存器 370 | GPx0CON 控制寄存器,用于设置GPIO接口的工作模式 371 | 0000 = Input 372 | 0001 = Output 373 | 0010 = 特殊功能 374 | 0011 ~ 1110 = Reserved 375 | GPx0DAT 数据寄存器 376 | 根据控制寄存器的配置,来决定DAT寄存器的用法。 377 | Input 读取DAT中的数据,用来获取外部处于什么状态。 378 | Output 向DAT写入数据,写入0则输出低电流,写入1则输出高电流。 379 | GPx0PUD 上/下拉电阻寄存器 380 | 上/下拉电阻寄存器的作用,当GPIO接口既不输入也不输出,的状态,然后就可以产拉电阻,让电路处于高阻状态,相当于电路是断的。 381 | 382 | 六、通过GPIO控制LED 383 | 1、通过阅读电路原理图找到硬件具体的接口。 384 | 2、通过阅读核心版电路图找到接口对应的GPIO编号。 385 | 3、阅读CPU芯片手册,找到对应地址,计算出相应的位以后要赋值的数据。 386 | 387 | GPJ0_3 接口要处于低电流状态,然后LED灯才会亮。 388 | GPJ0CON Address = 0xE020_0240 389 | [13~15]位 在不改变其它二进制位的情况下赋值为:0001b 390 | GPJ0DAT Address = 0xE020_0244 391 | [3]位 在不改变其它二进制位的情况下赋值为:0,让GPJ0_3输出低电流 392 | GPJ0PUD Address = 0xE020_0248 393 | [6~7]位 在不改变其它二进制位的情况下赋值为:00,禁用上/下拉电阻。 394 | 395 | 七、汇编语言调用C函数 396 | C代码的执行需要内存划分局域,可以借助链接脚本来规划内存的分布。 397 | 398 | 链接地址:我们希望程序在什么地址下执行,然后编译时编译就会以此地址计算相关跳转语句。 399 | 运行地址:程序实际运行时,所在的地址,运行地址如果和实际地址不匹配可能会出现跳转错误。 400 | 链接脚本能干什么:链接脚本就是一个规则文件,可以用它来指挥链接器的工作,链接器会根据链表脚本中的内存,合并目标文件,最终生成可执行程序。 401 | 链接脚本内容: 402 | SECTIONS{ //所有内容都会被它包含 403 | .代表当前位置 404 | = 用来赋值 405 | .text 表示代码段 406 | .data 表示数据段 407 | .bss 表示静态数据段 408 | } 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 一、串口通信的优点 437 | 串口通信以字节为单位收发数据,但确是使用一根线收一要线发,这样的好处是可以传输的距离很远,而且通信简单。 438 | 串口通信是嵌入式开发中非常常用的调试设备的方式。 439 | 440 | 二、串口通信的重要参数 441 | 1、波特率:该参数表示串口每秒传输的比特位数。 442 | 常用9600、115200,通信时传输距离越长,波特率就要越低。 443 | 2、数据位 444 | 表示在收到的数据包中,单纯的数据点多少位,如果仅传输标准的ASCII表中的字符(0~127),那么7位就够,如果是带扩展的则需要8位(0~255)。 445 | 3、奇偶校验位 446 | 通过统计数据中的高或低的个数,来实现数据检验的一种方式。如果常见的检测方式有:偶校验、奇校验、标记等方式。 447 | 4、停止位 448 | 用于标志一个数据的传输结构,可以取1位或者两位,它不仅标志着数据包的传输结束,也给通信双方提供的校正时钟的机会。 449 | 5、流控制 450 | 在双方通信时,由于速度不匹配,比如发送方比接收方速度快,这样可能会造成接收方缓冲区中的数据还没有读取,新的发送数据再次来临,这样就会造成数据被覆盖。 451 | 所谓的流控制就增加一个响应消息,用于标志接收方是否读取缓冲区里的数据。 452 | 453 | 二、通用异步接收器 454 | UART(通用异步接收器):是一种通用的串行数据总线,用于异步通信,总共有四个单元组成,分别是波特率发生单元、控制单元、发送器、接收器。 455 | volatile 456 | UART寄存器: 457 | 1、收发数据的GPIO接口的工作模式设置。 458 | GPA1CON, R/W, Address = 0xE020_0020 459 | 2、ULCON2, R/W, Address = 0xE290_0800 460 | 不使用红外线传输 461 | 无奇偶校验 462 | 1个停止位 463 | 8个数据们 464 | 3、UCON2, R/W, Address = 0xE290_0804 465 | 读取模式选择中断或轮询 466 | 发送模式选择中断或轮询 467 | 不发生中断信息 468 | 确定使用轮询 469 | 错误处理不使用中断 470 | 超时不产生中断 471 | 读取数据中断、发送数据中断 472 | 时钟选择默认 473 | DMA的读写缓冲区大小采用默认 474 | 4、UTRSTAT2, R, Address = 0xE290_0810 475 | 读缓冲区是否为空 476 | 发送是否完成 477 | 5、UTXH2, W, Address = 0xE290_0820 478 | 发送缓冲,直接把要发送的数据写入此寄存器 479 | 6、URXH2, R, Address = 0xE290_0824 480 | 接收到的数据会存储到此寄存器中 481 | 7、UBRDIV2, R/W, Address = 0xE290_0828 482 | DIV = (时钟频率 / (波特率 x 16)) -1 483 | 8、UDIVSLOT2, R/W, Address = 0xE290_082C 484 | UBRDIV2、UDIVSLOT2配合设置波特率 485 | (时钟频率 / (波特率 x 16))-1结果的小数部分*16, 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 一、中断的基本概念 513 | 1、什么是中断 514 | 当计算机在执行程序时,当出现异常情况(信号),计算机停止当前程序的运行,转而去处理异常情况,处理完成后再返回继续执行,这种情况叫中断。 515 | 2、中断的处理过程 516 | 在S5PV210中,ARM设计了一个完成的中断系统,中断程序独立于主程序之外,中断响应过程是: 517 | 1、主程序正常在执行,中断事件、中断源产生一个中断请求。 518 | 2、CPU接收到中断请求后,将主程序暂停,产生一个中断点。 519 | 3、中断系统响应中断请求,转而执行中断服务程序。 520 | 4、执行完成中断响应程序后返回到主程序的中断点处,继续执行主程序。 521 | 3、中断系统的优点 522 | 现在绝大多数的嵌入式芯片都具有中断系统,使用它的好处: 523 | 1、实现实时处理,根据CPU的内部功能模块(硬件上的)来响应中断请求,为其服务,不需要主程序做任务的监控,因此可以实时处理任务。 524 | 2、实现分时操作,根据需要设计若干个中断源,这样就可以同时响应多个外部设备的中断请求,只有在外部设备产生中断时,CPU才转而为它服务,这样可以让多个外部设备同时工作,实现分时操作。 525 | 3、故障处理,与轮询模式相比,中断处理异常情况、或故障时会更及时,可以实现紧急故障及时处理。 526 | 4、待机唤醒,嵌入式设备最大的一个特点就是低功耗,因此就需要具有休眠的功能,而从休眠状态快速切换到正常工作模式,就是通过中断实现的。 527 | 528 | 4、中断源、中断信号、中断向量 529 | 中断源:顾名思义,就是能够产生中断的源头(能够产生中断的一些内设或外设),S5PV210中有128个中断源,但有一些是空的,如:定时器、串行通信、数模转换器。 530 | 531 | 中断信号: 532 | 1、脉冲信号(上升或下降) 533 | 2、电平信号(高电平或低电平) 534 | 3、电平变化(高电平转低电平或低电平转高电平) 535 | 536 | 中断向量: 537 | 也叫异常向量,以CPU规则一个位置用于存储各异常模式的响应位置,而每个模式占4字节,因此无法直接存储异常响应程序,而只能存储一个地址,该地址指向真正的异常响应程序。 538 | 539 | 540 | 二、SDRAM内存 541 | 1、SDRAM简介 542 | 同步动态随机存取内存(synchronous dynamic random-access memory)简称SDRAM。 543 | DDR:就是DDR SDRAM,是SDRAM的升级版,意思是双倍速的SDRAM。 544 | DDR有好多种代:DDR1、DDR2、DDR3、DDR4、LPDDR(低功耗的LPDDR)。 545 | 2、SDRAM的特性 546 | SRAM (静态随机存取存储器):只需要保持通电即可,在使用过程中不需要做额外的操作,而SRAM在使用过程中需要每隔一段赶时间进行刷新一次,这样的就浪费了大量的时间。 547 | SRAM:容量小、价格高、速度快、上电就可用、掉电丢失、随机读写、总线式访问。 548 | SDRAM:容量大、价格低、动态刷新、需要初始化、速度慢、掉电丢失、随机读写、总线式访问。 549 | 由于SRAM和SDRAM的特性,所以就导致启动过程比较怪异、复杂,我们在裸机阶段学习一些硬件其实是为了学习uboot做准备,而学习uboot是为了学习内核,而学习内核的目的是为了编写驱动程序还有学习内核中优秀的代码、算法。 550 | 551 | 3、SDRAM芯片介绍 552 | PS:芯片的发展趋势,把很多外部芯片的功能集成到CPU内部形成SOC 553 | SDRAM芯片属于外设,也就是接在CPU外面,因为SDRAM不光要频繁的读写数据还要定时刷新,所以导致SDRAM芯片的温度特别高,而SOC本身的温度就特别高,如果两者结合在一起,估计可以煎鸡蛋。 554 | 开发板的原理上标注的是K4T1G164QQ,但实际上不是这个款芯片,而是一款与K4T1G164QQ兼容的国内半导体厂商生产的芯片。 555 | 全球做SDRAM芯片的厂商不多,一般都是二、三线厂家向一线厂家看齐,目的是为了兼容一线厂家的芯片,然后让在意成本的厂商选择它替代一线厂家的芯片,而二、三线厂家没有创新、研发、测试的成本,因此价格比较低。 556 | 由于这些行业现状导致SDRAM芯片的标准比较统一。 557 | 4、原理图中的SDRAM 558 | 由于S5PV210中只有两个DRAM接口,但通过查看原理图会发现有四个SDRAM芯片,而九鼎其实是通过并联方式把SDRAM芯片两两连接在一起,形成两个32们的内存芯片。 559 | DRAM0接口的范围:0x20000000~0x40000000 512M 560 | DRAM1接口的范围:0x40000000~0x80000000 1024M 561 | S5PV210一共可以支持1.5G的内存,而九鼎接的每个SDRAM芯片只有128M,也就相当两个256M,共512M内存。 562 | 563 | 三、重定位 564 | 1、链接地址 565 | 程序编译时指定的地址,通过-Ttext参数指定的地址,它是理论上程序在运行时所处的地址。 566 | 2、加载地址 567 | 程序在运行时实际所处理的地址。 568 | 注意:链接地址与加载地址不能保障一定一样。 569 | 3、位置有关代码 570 | 如果程序的链接地址与加载地址不符,程序就无法运行,或运行错误这中代码叫位置有关代码。 571 | 4、位置无关代码 572 | 如果程序的链接地址与加载地址不符,程序依然能够正确运行,这种代码叫位置无关代码。 573 | 可以简单的认为使用了BL的代码叫位置有关代码,而没有使用BL的叫位置无关代码。 574 | 5、再论S5PV210的启动过程 575 | 1、iROM中的固定程序会从SD中拷贝16~96KB的程序先进行环境初始化(设置时钟、初始化内存、开看门狗等)。 576 | 2、然后再把剩余的程序从SD中拷贝到SDRAM中。 577 | 3、然后把开发板上的所以资源都初始化好,为OS的运行做好准备。 578 | 4、然后拷贝OS到SDRAM中,启动OS。 579 | 6、什么是重定位 580 | 而uboot为了兼容大多数CPU并不会这样,而先把uboot的前96KB拷贝到SRAM中,然后初始化,再把完整的uboot拷贝到SDRAM中。 581 | 因此uboot的前96KB的程序就会在不同的位置运行两次,所以一定会出现链接地址与加载地址不符的情况。 582 | 具体的做法是先把uboot拷贝到SRAM中只运行当量的位置无关代码,然后再把程序拷贝到正确的位置,种行为叫重定位。 583 | 584 | 585 | 586 | 587 | -------------------------------------------------------------------------------- /C++ - 副本.cpp: -------------------------------------------------------------------------------- 1 | C与C++的区别? 2 | 一、C++介绍 3 | 本贾尼·斯特劳斯特卢普,与1979年4月份贝尔实验室的本贾尼博士在分析UNIX系统分布内核流量分析时,希望有一种有效的更加模块化的工具。 4 | 1979年10月完成了预处理器Cpre,为C增加了类机制,也就是面向对象,1983年完成了C++的第一个版本,C with classes也就是C++。 5 | C++与C的不同点: 6 | 1、C++完全兼容C的所有语法(内容) 7 | 2、支持面向对象的编程思想 8 | 3、支持运算符重载 9 | 4、支持泛型编程、模板 10 | 5、支持异常处理 11 | 6、类型检查严格 12 | 13 | 二、第一个C++程序 14 | 1、文件扩展名 15 | .cpp .cc .C .cxx 16 | 2、编译器 17 | g++ 大多数系统需要额外安装,Ubuntu系统下的安装命令: 18 | sudo apt-get update 19 | sudo apt-get install g++ 20 | gcc也可以继续使用,但需要增加参数 -xC++ -lstdc++ 21 | 3、头文件 22 | #include 23 | #include 可以继续使用,但C++建议使用 #include 24 | 4、输入/输出 25 | cin << 输入数据 26 | cout >> 输出数据 27 | cin/cout会自动识别类型 28 | scanf/printf可以继续使用 29 | 注意:cout和cin是类对象,而scanf/printf是标准库函数。 30 | 5、增加了名字空间 31 | std::cout 32 | using namespace std; 33 | 34 | 三、名字空间 35 | 1、什么是名字空间 36 | 在C++中经常使用多个独立开发的库来完成项目,由于库的作者或开发人员没见过面,因此命名冲突在所难免。 37 | 2、为什么需要名字空间 38 | 在项目中函数名、全局变量、结构、联合、枚举、类,非常有可能名字冲突,而名字空间就对这些命名进行逻辑空间划分(不是物理单元划分), 39 | 为了解决命名冲突,C++之父为防止命名冲突给C++设计一个名字空间的机制。 40 | 通过使用namespace XXX把库中的变量、函数、类型、结构等包含在名字空间中,形成自己的作用域,避免名字冲突。 41 | namespace xxx 42 | { 43 | 44 | }// 没有分号 45 | 注意:名字空间也是一种标识符,在同一作用域下不能重名。 46 | 47 | 3、同名的名字空间有自动合并(为了声明和定义可以分开写) 48 | 同名的名字空间中如果有重名的依然会命名冲突 49 | 50 | 4、名字空间的使用方法 51 | ::域限定符 52 | 空间名::标识符 // 使用麻烦,但是非常安全 53 | using namespace 空间名; 把空间中定义的标识符导入到当前代码中 54 | 不建议这样使用,相当于把垃圾分类后,又导入同一个垃圾车,依然会冲突 55 | 56 | 5、无名名字空间 57 | 不属于任何名字空间中的标识符,隶属于无名名字空间。 58 | 无名名字空间中的成员使用 ::标识符 进行访问。 59 | 如何访问被屏蔽的全局变量。 60 | 61 | 6、名字空间的嵌套 62 | 名字空间内部可以再定义名字空间,这种名字空间嵌套 63 | 内层的名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符。 64 | 多层的名字空间在使用时逐层分解。 65 | n1::n2::num; 66 | namespace n1 67 | { 68 | int num = 1; 69 | namespace n2 70 | { 71 | int num = 2; 72 | namespace n3 73 | { 74 | 75 | } 76 | } 77 | } 78 | 79 | 7、可以给名字空间取别名 80 | 由于名字空间可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给名字空间取别名来解决这类问题。 81 | namespace n123 = n1::n2::n3; 82 | 83 | 四、C++的结构 84 | 1、不再需要 typedef ,在定义结构变量时,可以省略struct关键字 85 | 2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针。 86 | 3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。 87 | 4、可以继承,可以设置成员的访问权限(面向对象)。 88 | 89 | 五、C++的联合 90 | 1、不再需要 typedef ,在定义结构变量时,可以省略union关键字 91 | 2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针。 92 | 3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。 93 | 94 | 六、C++的枚举 95 | 1、定义、使用方法与C语言基本一致。 96 | 2、类型检查比C语言更严格 97 | 98 | 七、C++的布尔类型 99 | 1、C++具有真的布尔类型,bool是C++中的关键字,在C语言中使用布尔类型需要导入头文件stdbool.h(在C11中bool应该是数据类型了)。 100 | 2、在C++中 true false 是关键字,而在C语言中不是。 101 | 3、在C++中 true false 是1字节,而C语言中是4字节。 102 | 103 | 八、C++的void* 104 | 1、C语言中void* 可以与任意类型指针 自动转换。 105 | 2、C++中void*不能给其他类型的指针直接赋值,必须强制类型转换,但其他类型的指针可以自动给void*赋值。 106 | 3、C++为什么这样修改void*? 107 | 为了更安全,所以C++类型检查更严格。 108 | C++可以自动识别类型,对万能指针的需求不再那么强烈。 109 | 110 | 九、操作符别名 111 | 某些特殊语言的键没有~,&符合,所以C++标准委员会为了让C++更有竞争力,为符号定义了一些别名,让这些小语种也可以愉快编写C++代码 112 | and && 113 | or || 114 | not ! 115 | { <% 116 | } %> 117 | # :% 118 | 119 | 十、函数重载(重载、隐藏、重写) 120 | 1、函数重载 121 | 在同一作用域下,函数名相同,参数列表不同的函数,构成重载关系。 122 | 2、重载实现的机制 123 | C++代码在编译时会把函数的参数类型添加到参数名中,借助这个方式来实现函数重载,也就是C++的函数在编译期间经历换名的过程。 124 | 因此,C++代码不能调用C函数(C语言编译器编译出的函数) 125 | 3、extern "C" {} 126 | 告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件可以与C目标文件合并生成可执行程序)。 127 | 如果C想调用C++编译出的函数,需要将C++函数的定义用extern "C"包括一下。 128 | 注意:如果两个函数名一样,一定会冲突。 129 | 4、重载和作用域 130 | 函数的重载关系发生在同一作用域下,不同作用域下的同名函数,构成隐藏关系。 131 | 5、重载解析 132 | 当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫重载解析。 133 | 实参的类型和形参的匹配情况有三种: 134 | 1、编译器找到与实参最佳的匹配函数,编译器将生成调用代码。 135 | 2、编译找不到匹配函数,编译器将给出错误信息。 136 | 3、编译器找到多个匹配函数,但没有一个最佳的,这种错误叫二义性。 137 | 在大多数情况下编译器都能立即找到一个最佳的调用版本,但如果没有,编译就会进行类型提升,这样备选函数中就可能具有多个可调用 138 | 的版本,这样就可能产生二义性错误。 139 | 6、确定存在函数的三个步骤 140 | 1)候选函数 141 | 函数调用的第一步就是确定所有可调用的函数的集合(函数名、作用域),该集合中的函数就是候选函数。 142 | 2)选择可行函数 143 | 从候选函数中选择一个或多个函数,选择的标准是参数个数相同,而且通过类型提升实参可被隐式转换为形参。 144 | 3)寻找最佳匹配 145 | 优先每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的字节数。 146 | 7、指针类型会对函数重载造成影响 147 | C++函数的形参如果是指针类型,编译时函数名中会追加Px。 148 | 149 | 十一、默认形参 150 | 1、在C++中函数的形参可以设置默认值,调用函数,如果没有提供实参数,则使用默认形参。 151 | 2、如果形参只有一部分设置了默认形参,则必须靠右排列。 152 | 3、函数的默认形参是在编译阶段确定的,因此只能使用常量、常量表达式、全局变量数据作为默认值。 153 | 4、如果函数的声明和定义需要分开,那么默认形参设置在声明、定义,还是声明定义都需要设置。 154 | 5、默认形参会对函数重载造成影响,设置默认形参时一定要慎重。 155 | 156 | 十二、内联函数 157 | 1、普通函数调用时是生成调用指令(跳转),然后当代码执行到调用位置时跳转到函数所在的代码段执行。 158 | 2、内联函数就把函数编译好的二进制指令直接复制到函数的调用位置。 159 | 3、内联函数的优点就是提高程序的运行速度(因为没有跳转,也不需要返回),但这样会导致可执行文件增大(冗余),也就是牺牲空间来换取时间。 160 | 4、内联分为显示内联和隐式内联 161 | 显示内联:在函数前 inline(C语言C99标准也支持) 162 | 隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数。 163 | 5、宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间,所以宏函数与内联函数的区别(优缺点)? 164 | 1、宏函数不是真正的函数,只是代码替换,不会有参数压栈、出栈以及返回值,也不会检查参数类型,因此所有类型都能使用,但这样会 165 | 有安全隐患。 166 | 2、内联函数是真正的函数,被调用时会进行传参,会进行压栈、出栈,可以有返回值,并会严格检查参数类型,这样就不能通用,如果想被 167 | 多种类型调用需要重载。 168 | 6、内联适用的条件 169 | 由于内联会造成可执行文件变大,并增加内存开销,因此只有频繁调用的简单函数适合作为内联。 170 | 调用比较少的复杂函数,内联后并不显著提高性能,不足以抵消牺牲空间带来的损失,所以不适合内联。 171 | 带有递归特性和动态绑定特性的函数,无法实施内联,因此编译器会忽略声明部分的inline关键字。 172 | 173 | 十三、引用 174 | 引用就是取艺名(别名)。 175 | 1、引用的基本特性 176 | 引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号。 177 | 1、引用必须初始化,不存在空引用,但有悬空引用(变量死了,名还留着)。 178 | 2、可以引用无名对象和临时对象,但必须使用常引用。 179 | 3、引用不能更换目标 180 | 4、引用目标如果具有const属性,引用也需要具有const属性。 181 | 引用一旦完成了定义和初始化就和普通变量名一样,它就代表了目标,一经引用终身不能再引用其他目标。 182 | 2、引用型参数 183 | 引用当作函数的参数能达到指针同样的效果,但不具备指针的危险,还比指针方便。 184 | 引用可以非常简单的实现函数间共享变量的目的,而且是否使用引用由被调函数说了算。 185 | 引用当作函数的参数还能提高传递参数效率,指针至少还需要4字节内存,而引用只需要增加一条标识符与内存之间的绑定(映射)。 186 | 3、引用型返回值 187 | 不要返回局部变量的引用,会造成悬空引用。 188 | 如果返回值是一个临时值(右值),如果非要使用引用接收的话,必须使用常引用。 189 | 注意:C++中的引用时一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数)。 190 | 练习1:实现一个C++版本的swap函数。 191 | #include 192 | using namespace std; 193 | 194 | void swap(int& a,int& b) //引用 195 | { 196 | int temp = a; 197 | a = b; 198 | b = temp; 199 | } 200 | 201 | int main() 202 | { 203 | int a=3,b=4; 204 | swap(a,b); 205 | cout< 226 | using namespace std; 227 | 228 | struct Student 229 | { 230 | Student(void) 231 | { 232 | cout<<"我是构造函数,创建对象时,我就会执行" << endl; 233 | } 234 | 235 | ~Student(void) 236 | { 237 | cout<< "我是析构函数,释放对象时,我就会执行" << endl; 238 | } 239 | }; 240 | 241 | int main() 242 | { 243 | int* p = new int; 244 | *p = 10; 245 | 246 | cout<< *p < 276 | using namespace std; 277 | 278 | int main() 279 | { 280 | int *p = NULL; 281 | try{ 282 | p = new int[~0]; 283 | } 284 | catch(std::bad_alloc& ex) 285 | { 286 | cout << "error" << endl; 287 | } 288 | } 289 | 290 | 十五、强制类型转换 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 面向过程编程: 322 | 关注是问题解决的过程步骤,算法 323 | 324 | 面向对象编程: 325 | 关注的是谁能解决问题(类),需要什么样的数据(成员变量),具备什么样的技能(成员函数)才能解决问题。 326 | 抽象:找出一个能够解决问题的“对象”(观察研究对象),找出解决所必须的数据(属性)、功能(成员函数)。 327 | 封装:把抽象的结果,归结为一个类(数据类型),然后实例化出类对象,设置对象的属性,调用对象的功能达到解决问题的目的。 328 | 继承:在解决问题前,先寻找之前的类能不能解决问题,或解决部分问题,如果可以则把旧的类继承后再次拓展,来缩短解决问题的时间,降低 329 | 解决问题的难度。 330 | 多态:对象的多种形态,外部看到一个对象发出指令,对象会根据自身情况做出独特的反应。 331 | 332 | 一、类和对象 333 | 1、通过分析对象的属性和行为设计出一个类。 334 | 2、类就是数据类型 335 | 简单类型:只能表示一个属性(变量),C/C++内建数据类型 336 | 数组类型:可以表示多个属性(变量),类型必须相同。 337 | 结构类型:可以表示多个属性(变量),但缺少行为(函数)。 338 | 类类型:即能表示属性,也能表示行为,一直复合数据类型。 339 | 3、对象就是类这种数据类型创建出的实例,相当于结构变量。 340 | class Student 341 | { 342 | 属性(成员变量); 343 | 行为(成员函数); 344 | }; 345 | 346 | Student stu; 347 | 348 | 二、类的定义与实例化 349 | 1、类的一般形式 350 | class 类名 : 继承方式 父类 351 | { 352 | public/private/protected: // 访问控制限制符 353 | 354 | 成员变量; 355 | 356 | // 构造函数 357 | 类名(形参表) 358 | { 359 | 360 | } 361 | 362 | // 析构函数 363 | ~类名(void) 364 | { 365 | 366 | } 367 | }; 368 | 2、类的访问控制限定符 369 | public:公有成员,在任何位置都可以访问 370 | private:私有成员,只能在类(自己)的成员函数中访问 371 | protected:受保护成员,只能在类(自己)和子类中访问 372 | 注意:类中的成员变量、成员函数默认是 private,结构中的成员和成员函数默认是 public。 373 | 注意:C++中类和结构的区别只有成员函数和成员变量的默认访问权限不同。 374 | 3、构造函数 375 | 1)什么是构造函数:类的同名函数就是构造函数,没有返回值。 376 | 2)什么时候调用,谁调用,调用几次? 377 | 创建类对象时会被自动调用(每创建一个类对象,就会调用一次),对象整个生命周期中一定会被 378 | 调用一次,只能被调用一次。 379 | 3)负责干什么 380 | 成员变量的初始化,分配相关资源,设置对象的初始状态。 381 | 382 | class 类名 : 继承方式 父类 383 | { 384 | // 构造函数 385 | 类名(形参表) 386 | { 387 | 388 | } 389 | }; 390 | 4、类型的创建过程 391 | 1.分配类型所需要空间,无论栈还是堆。 392 | 2.传递实参调用构造函数,完成如下任务: 393 | 1)根据继承表依次调用父类的构造函数 394 | 2)根据成员变量的顺序依次调用成员变量的构造函数。 395 | 3)执行构造函数体中的代码。 396 | 注意:执行构造函数的代码是整个构造函数的最后一步。 397 | 要保证构造函数代码所需要的一切资源和先决条件在该代码执行前已经准备充分,并得到正确的初始化。 398 | 5、对象的创建方法 399 | 1.在栈上创建:类名 对象;// 不需要括号 400 | 类名 对象(实参); 401 | 2.在堆上创建:类名* 对象指针 = new 类名; 402 | 类名* 对象指针 = new 类名(实参); 403 | 3.创建多个对象: 404 | 类名 对象 = {类名(实参),类名(实参),类名(实参)}; 405 | 类名* 对象指针 = new 类名[n]{类名(实参),类名(实参)}; 406 | 注意:通过malloc创建的类对象不能调用构造函数。 407 | 注意:通过new[]创建的对象,一定要通过delete[]释放。 408 | 6、类的声明、实现、调用 409 | 1.在头文件中声明 410 | class 类名 : 继承方式 父类 411 | { 412 | 成员变量; 413 | public: // 访问控制限制符 414 | // 构造函数 415 | 类名(形参表); 416 | // 析构函数 417 | ~类名(void); 418 | // 其他成员函数 419 | 返回值 函数名(参数列表); 420 | }; 421 | 422 | 2.源文件实现类的相关函数 423 | 返回值 类名::函数名(参数列表) 424 | { 425 | 426 | } 427 | 428 | 3.调用时只需要导入头文件,然后与类函数所在的源文件一起编译即可。 429 | 注意:如果一个类内容不多,可以考虑在头文件中完全实现。 430 | 也可以只在头文件中实现一些简单的成员函数。 431 | 注意:类中自动生成的函数,在源文件中实现时,也需要在头文件中声明。 432 | 433 | 三、构造函数与初始化列表 434 | 1、构造函数可以被重载(同一个名字的函数有多个不同版本) 435 | 2、缺省构造是编译器自动生成的一个什么都不做的构造函数(唯一的作用就是避免编译错误)。 436 | 注意:当类实现一个有参构造时,缺省构造就不会再自动生成,如果有需要必须显示地写出来。 437 | 3、无参构造未必无参,当给有参构造的所有参数设置默认形参,调用这种构造函数就不需要传参。 438 | 439 | 注意:所谓的“编译器生成的某某函数”其实不是真正语法意义上的函数,而是功能意义上的函数,编译器作为可执行指令的生成者,它会直接生成 440 | 具有某项功能的二进制指令,不需要借助高级语言语义上的函数完成此任务。 441 | 442 | 注意:如果一个类是其他类的成员变量,那么一定要保证它有一个无参构造,当B的构造函数执行时会执行成员变量的无参构造,而此时类B是无 443 | 法给类A成员变量提供参数的。 444 | 4、单参构造与类型转换 445 | 如果构造函数的参数只有一个,那么Test t = n语句就不会出错,它会自动调用单参构造来达到类型转换的效果。 446 | 如果想禁止这种类型转换需要在单参构造前加 explicit 447 | 5、初始化列表 448 | 为类成员进行初始化用的。 449 | 构造函数(参数):成员1(参数1),成员2(参数2)... 450 | const int num; 451 | Test(int n):num(n) 452 | { 453 | } 454 | 通过初始化列表可以给类成员变量传递参数,以此调用类成员的有参构造。 455 | 初始化列表也可以给 const 成员、引用成员进行初始化。 456 | #include 457 | using namespace std; 458 | 459 | class A 460 | { 461 | public: 462 | int num; 463 | A(int _num) 464 | { 465 | num = _num; 466 | cout<<"我A的有参构造"< 497 | using namespace std; 498 | 499 | class A 500 | { 501 | public: 502 | A(int n) 503 | { 504 | cout<<"A"< 599 | #include 600 | using namespace std; 601 | 602 | class User 603 | { 604 | char name[20]; 605 | char pass[7]; 606 | public: 607 | User(const char* name,const char* pass) 608 | { 609 | strcpy(this->name,name); 610 | strcpy(this->pass,pass); 611 | //show(); 612 | } 613 | 614 | User& func(void) 615 | { 616 | return *this; 617 | } 618 | 619 | void show(void)//隐藏this指针 620 | { 621 | cout<< name << " "<< pass < 665 | #include 666 | #include 667 | using namespace std; 668 | 669 | class A 670 | { 671 | public: 672 | A(void) 673 | { 674 | cout << "A 's 构造" <name = new char[strlen(name)+1]; 706 | strcpy(this->name,name); 707 | this->pass = new char[strlen(pass)+1]; 708 | strcpy(this->pass,pass); 709 | cout<< "构造"< 父类构造-> 成员构造-> 自己构造 743 | 父类构造:按照继承表从左到右依次构造。 744 | 成员构造:按照声明顺序从上至下依次构造。 745 | 释放:自己析构-> 成员析构-> 父类析构-> 释放内存(对象) 746 | 成员析构:按照声明顺序从下到上依次构造。 747 | 父类析构:按照继承表从右到左依次构造。 748 | 749 | 四、拷贝构造 750 | 拷贝构造又称为复制构造,是一种特殊的构造函数,它是使用一个现有的旧对象构造一个新的对象时调用的函数,只有一个引用型的参数(对象本身)。 751 | 类名(类& ) 752 | { 753 | 754 | } 755 | 拷贝构造的参数应该加 const 保护,但编译器并没有强行限制。 756 | 编译器会自己生成一个拷贝构造函数,它负责把旧对象中的所有数据拷贝给新创建的对象。 757 | 758 | 深拷贝与浅拷贝的区别: 759 | 如果类成员有指针,浅拷贝只拷贝指针变量的值,而深拷贝指针变量所指向的目标。 760 | 什么情况下需要实现拷贝构造: 761 | 当类成员中没有指针成员,此时默认的拷贝构造(浅拷贝)就无法完成任务,需要自己动手实现拷贝构造(深拷贝)。 762 | 什么情况下会调用拷贝构造: 763 | 1、使用旧对象给新对象赋值时 764 | User user1 = user; 765 | 2、使用对象当作函数的参数,当调用函数时,就会一起调用拷贝构造。 766 | #include 767 | #include 768 | 769 | using namespace std; 770 | 771 | class User 772 | { 773 | char* name; 774 | char pass[7]; 775 | int id; 776 | public: 777 | User(const char* name,const char* pass) 778 | { 779 | this->name = new char[strlen(name)+1]; 780 | strcpy(this->name,name); 781 | strcpy(this->pass,pass); 782 | } 783 | void show(void) 784 | { 785 | cout< 831 | #include 832 | 833 | using namespace std; 834 | 835 | class User 836 | { 837 | char* name; 838 | char pass[7]; 839 | public: 840 | User(const char* name,const char* pass) 841 | { 842 | this->name = new char[strlen(name)+1]; 843 | strcpy(this->name,name); 844 | strcpy(this->pass,pass); 845 | } 846 | void show(void) 847 | { 848 | cout<) 911 | 912 | 七、静态成员 913 | 类成员一旦被 static 修饰就会变成静态成员,而是单独一份存储在bss或data内存段中,所有的类对象共享(静态成员属于类,而不属于某个对象)。 914 | 静态成员在类内声明,但必须在类外定义、初始化。与成员函数一样需要加“类名::”限定符表示它属于哪个类,但不需要再额外增加 static 915 | 916 | 成员函数也可以被static修饰,这种函数叫静态成员函数,这种成员没有this指针,因此在静态函数中不能直接访问类的成员,但可以直接访问 917 | 静态成员,但可以直接访问静态成员变量、静态成员函数。 918 | 静态成员变量、函数依然受访问控制限定符的影响。 919 | 因为在代码编译完成后,静态成员已经定义完成(有了存储空间),一次可以不用活类对象而直接调用,类名::静态成员名 920 | 921 | 静态成员变量可以被当做全局变量来使用(访问限定符必须是public),静态成员函数可以当作类的接口,实现对类的管理。 922 | 923 | 八、单例模式 924 | 什么是单例模式,只能创建出一个类对象(只有一实际的实例)的叫单例模式。 925 | 单例模式的应用场景: 926 | Windows系统的任务管理器 927 | Linux/Unix系统的日志系统 928 | 网站的访问计数器 929 | 服务端程序的连接池、线程池、数据池 930 | 获取单一对象的方法: 931 | 1、定义全局(C语言),但不受控制,防君子不能防小人。 932 | 2、专门写一个类,把类的构造函数设置私有,借助静态成员函数提供一个接口,以此来获取唯一的实例。 933 | C++如何实现单例: 934 | 1、禁止类的外部创建类对象:构造函数设置私有 935 | 2、类自己维护一个唯一的实例:使用静态指针指向 936 | 3、提供一个获取实例的方法:静态成员函数获取静态指针 937 | 938 | 饿汉模式: 939 | 将单例类的唯一实例对象定义为成员变量,当程序开始运行时,实例对象就已经创建完成 940 | 优点:加载进程时,静态创建单例对象,线程安全。 941 | 缺点:无论使用与否,总要创建,浪费内存。 942 | 懒汉模式: 943 | 用静态成员指针来指向单例类的唯一实例对象,只有真正调用获取实例的静态接口时,实例对象才被创建。 944 | 优点:什么时候用什么时候创建,节约内存。 945 | 缺点:在第一次调用获取实例对象的静态接口时,才真正创建,如果在多线程操作情况下有可能被创建出多个实例对象(虽然可能性很低), 946 | 存在线程不安全问题。 947 | 948 | 总结:C语言与C++有哪些不同点 949 | 内存管理 malloc/free new/delete 950 | static 951 | const 952 | void* 953 | 字符串:string系列函数 string类 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 一、操作符函数重载 984 | 什么是操作符函数:在C++中针对类类型的对象的运算符,由于它们肯定不支持真正的运算操作,因此编译器会将它们翻译成函数,这种就叫做 985 | 操作符函数(运算符函数)。 986 | 编译器把运算翻译成运算符函数,可以针对自定义的类类型设计它独有的运算功能。 987 | 其实各种运算符已经具备一些功能,再次实现它的就是叫作运算符重载。 988 | 989 | 双目运算符: 990 | a+b 991 | 成员函数 992 | a.operator+(b); 993 | 全局函数 994 | operator+(a,b); 995 | 单目运算符: 996 | !a 997 | 成员函数 998 | a.operator!(void); 999 | 全局函数 1000 | operator!(a); 1001 | 1002 | 二、双目操作符函数重载 1003 | 成员函数: 1004 | const 类对象 operator#(const 类& that) const 1005 | { 1006 | return 类(参数#参数); 1007 | } 1008 | 注意:双目录运算符的运算结果是个右值,返回值应该加 const ,然后为了const对象能够调用,参数应写const,函数也应该具备const属性。 1009 | 1010 | 全局函数: 1011 | const 类 operator#(const 类& a,const 类& b) 1012 | { 1013 | 1014 | } 1015 | 注意:全局函数不是成员函数,可能会需要访问到类的私有成员,解决这种问题可以把函数声明为类的友元函数(友元不是成员)。 1016 | 友元:在类的外部想访问类的私有成员(public/protected/private)时,需要把所在的函数声明为友元,但是友元只是朋友,因此它只有访 1017 | 问权,没有实际的拥有权(其根本原因是它没有this指针)。 1018 | 友元声明:把函数的声明写一份到类中,然后在声明前加上friend 关键字。使用友元即可把操作符函数定义为全局的,也可以确保类的封装性。 1019 | 注意:友元函数与成员函数不会构成重载关系,因此它们不在同一个作用域内。 1020 | 1021 | 三、赋值类型的双目操作符 1022 | 成员 1023 | 类 operator#(void) 1024 | { 1025 | 1026 | } 1027 | 全局 1028 | 类 operator#(const 类& that) 1029 | { 1030 | 1031 | } 1032 | 1、获取单参构造成赋值运算的调用方式。 1033 | String str = "xxx"; // 会调用单参构造,而不调用赋值运算符 1034 | str = "hhh"; 1035 | 1036 | 2、左操作数据不能具有const属性 1037 | 1.成员函数不能是常函数 1038 | 2.全局函数第一个参数不能有const属性 1039 | 3、返回值应该都(成员/全局)具备const属性 1040 | 1041 | 四、单目操作符函数重载 1042 | -,~,!,&,*,->,++,-- 1043 | 成员 1044 | const 类 operator#(void) const 1045 | { 1046 | 1047 | } 1048 | 全局 1049 | const 类 operator#(const 类& that) 1050 | { 1051 | 1052 | } 1053 | 前++/-- 1054 | 类& operator#(void) 1055 | { 1056 | 1057 | } 1058 | 类& operator#(类& that) 1059 | { 1060 | 1061 | } 1062 | 后++/--(哑元) 1063 | const 类& operator#(int) 1064 | { 1065 | 1066 | } 1067 | const 类& operator#(类& that,int) 1068 | { 1069 | 1070 | } 1071 | 1072 | 五、输入输出操作符重载 1073 | cout 是 ostream 类型的对象,cin 是 istream 类型的对象。 1074 | 如果<>运算实现为成员函数,那么调用者应该是ostream/istream,而我们无权增加标准库的代码,因此输入/输出运算符只能定义为全局函数。 1075 | 1076 | ostream& operator<<(ostream& os,const 类& n) 1077 | { 1078 | 1079 | } 1080 | 1081 | istream& operator>>(istream& os,类& n) 1082 | { 1083 | 1084 | } 1085 | 1086 | 注意:在输入输出过程中,cin/cout会记录错误标志,因此不能加const属性。 1087 | 1088 | 六、特殊操作符的重载(笔试面试比较重要) 1089 | 1、下标操作符 [],常用于在容器类型中以下标方式获取元素。 1090 | 类型& operator[](int i) 1091 | { 1092 | 1093 | } 1094 | 2、函数操作符(),一个类如果重载函数操作符,那么它的对象就可以像函数一样使用,参数的个数、返回值类型,可以不确定,它是唯一一个 1095 | 可以参数有缺省参数的操作符。 1096 | #include 1097 | #include 1098 | using namespace std; 1099 | 1100 | class Array 1101 | { 1102 | int* arr; 1103 | size_t len; 1104 | public: 1105 | Array(size_t len):len(len) 1106 | { 1107 | arr = new int[len]; 1108 | } 1109 | 1110 | void operator()(void) 1111 | { 1112 | cout<<"emmm"<= len) 1118 | { 1119 | cout<<"下标错误"< 1139 | 如果一个类重载了*和->,那么它的对象就可以像指针一样使用。 1140 | 所谓的智能指针就是一种类对象,它支持解引用和成员访问操作符。 1141 | 4、智能指针 1142 | 常规指针的缺点: 1143 | 当一个常规指针离开它的作用域时,只有该指针所占用的空间会被释放,而它指向的内存空间能否被释放就不一定了,在一些特殊情 1144 | 况(人为、业务逻辑特殊)free或delete没有执行,就会形成内存泄漏。 1145 | 智能指针的优点: 1146 | 智能指针是一个封装了常规指针的类类型对象,当它离开作用域时,它的析构函数会自动执行,它的析构函数会负责释放常规指针所 1147 | 指向的动态内存(以正确方式创建的智能指针,它的析构函数才会正确执行)。 1148 | 智能指针和常规指针的相同点:都支持*和->运算。 1149 | 智能指针和常规指针的不同点: 1150 | 任何时候,一个对象只能使用一个智能指针来指向,而常规指针可以指向多次。 1151 | 只能指针的赋值操作需要经过拷贝构造和赋值构造特殊处理(深拷贝)。 1152 | #include 1153 | using namespace std; 1154 | 1155 | class Int 1156 | { 1157 | public: 1158 | int val; 1159 | Int(int val=0):val(val){ } 1160 | 1161 | void set_val(int val) 1162 | { 1163 | this->val = val; 1164 | } 1165 | int get_val(void) 1166 | { 1167 | return val; 1168 | } 1169 | 1170 | Int& operator=(const int val) 1171 | { 1172 | this->val = val; 1173 | return *this; 1174 | } 1175 | 1176 | ~Int(void) 1177 | { 1178 | cout<<"我是Int的析构函数"<>(ostream& os,Int& n); 1182 | }; 1183 | ostream& operator<<(ostream& os,Int& n) 1184 | { 1185 | return os< 1214 | 用法:auto_ptr<指向的类型> 指针变量名(对象的地址) 1215 | auto_ptr的局限性: 1216 | 不能跨作用域使用,一旦离开作用域指针变量会释放它指向的对象也会释放。 1217 | 不能放入标准容器。 1218 | 不能指向对象数组。 1219 | #include 1220 | #include 1221 | using namespace std; 1222 | 1223 | class A 1224 | { 1225 | public: 1226 | A(void) 1227 | { 1228 | cout<<"构造"< ptr(new A); 1246 | (*ptr).show(); 1247 | } 1248 | 1249 | 5、new/delete/new[]/delete[]运算符重载 1250 | 1.C++缺省的堆内存管理器速度较慢,重载new/delete底层使用malloc/free可以提高运行速度。 1251 | 2.new在失败会产生异常,而每次使用new时为了安全都应该进行异常捕获,而重载new操作符只需要在操作符函数中进行一次错误处理即可。 1252 | 3.在一些占字节数比较小的类,频繁使用new,可能会产生大量的内存碎片,而重载new操作符后,可以适当的扩大每次申请的字节数,减 1253 | 少内存碎片产生的机率。 1254 | 4.重载 new/delete 可以记录堆内存使用的信息 1255 | 5.重载 delete 可以检查到释放内存失败时的信息,检查到内存泄漏。 1256 | 1257 | 七、重载操作符的限制 1258 | 1、不能重载的操作符 1259 | 域限定符 :: 1260 | 直接成员访问操作符 . 1261 | 三目操作符 ?: 1262 | 字节长度操作符 sizeof 1263 | 类型信息操作符 typeid 1264 | 2、重载操作符不能修改操作符的优先级 1265 | 3、无法重载所有基本类型的操作符运算 1266 | 4、不能修改操作符的参数个数 1267 | 5、不能发明新的操作符 1268 | 1269 | 关于操作符重载的建议: 1270 | 1、在重载操作符时要根据操作符实际的功能和意义来确定具体参数,返回值,是否具有const属性,返回值是否是引用或者临时对象。 1271 | 2、重载操作符要符合情理(要有意义),要以实际用途为前提。 1272 | 3、重载操作符的意义是为了让对象的操作更简单、方便,提高代码的可读性,而不是为了炫技。 1273 | 4、重载操作符要与默认的操作符的功能、运算规则一致,不要出现反人类的操作。 1274 | #define ture 0 1275 | #define false 1 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 一、类的继承 1312 | 1、共性与个性 1313 | 表达不同类型事物之间公有的属性和行为。 1314 | 个性用于刻画每种事物特有的属性和行为。 1315 | 2、共性表示为父类(基类),个性表示为子类(派生类)。 1316 | 子类继承自父类 1317 | 基类派生出子类 1318 | 1319 | 二、继承的基本语法 1320 | 1、继承表 1321 | 一个子类可以同时继承零到多个父类,每个父类的继承方式可以相同也可以不同。 1322 | class 子类:继承方式1 父类1,继承方式2 父类2,... 1323 | { 1324 | 1325 | } 1326 | 2、继承方式 1327 | public 公有继承:父类的特性可通过子类向外扩展。 1328 | private 私有继承:父类的特性只能为子类所有。 1329 | protected 保护继承:父类的特性只能在继承链内扩展。 1330 | 1331 | 三、继承的基本特点 1332 | 1、公共特点(所有继承都有的特点) 1333 | 子类对象可以当作父类对象使用,子类对象与父类没有本质上的区别。 1334 | 子类的逻辑空间小于父类,但它的物理空间要大于等于父类。 1335 | 子类对象 IS A 父类对象 1336 | 2、向上和向下转换(造型) 1337 | 从子类到父类:子类的指针或引用可以隐式转换成父类的指针或引用,这是一种缩小类型的转换,对于编译器来说是安全的。 1338 | 从父类到子类:父类的指针或引用不可以转换成子类的指针或引用,这是一种扩大类型的转换,在编译器看来是危险的。(子类的指针指 1339 | 向父类的对象,不安全) 1340 | 编译器仅仅是检查指针或引用的数据类型,而对实际引用的目标对象不关心(构成多态的基础)。 1341 | 类型一致:父类的指针或引用实际的目标类型是否需要转换成实际的指针或引用由程序自己决定。 1342 | 3、子类会继承父类的所有成员(公有,私有,保护) 1343 | 4、子类会隐藏父类的同名成员 1344 | 1.可以通过域限定符 父类::隐藏成员 进行访问父类中的隐藏成员 1345 | 2.可以使用父类的指针或引用来指向子类对象,然后访问父类中的隐藏成员。 1346 | 5、虽然子类继承所有父类中的成员,但不能访问父类中的私有成员。 1347 | 1348 | 四、继承方式影响访问控制 1349 | 1350 | 1351 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 一、子类的构造、析构、拷贝 1384 | 1、子类的构造在执行它的构造函数前会根据继承表的顺序执行父类的构造函数。 1385 | 默认执行父类的无参构造 1386 | 显示调用有参构造,在子类的构造函数后,初始化列表中显示调用父类的有参构造函数。 1387 | 2、子类在它的析构执行完后,会根据继承表的顺序,逆顺序执行父类的析构函数。 1388 | 注意:父类的指针可以指向子类对象,当通过父类指针释放对象时,只会调用父类的析构函数,而这种析构方式有可能造成内存泄漏。 1389 | 3、当使用子类对象来初始化新的子类对象时,会自动调用子类缺省的拷贝构造函数,并且会先调用父类缺省的拷贝构造函数。 1390 | 如果子类中实现的拷贝构造,需要显式调用父类拷贝构造,否则就会调用无参构造。 1391 | 1392 | 二、私有继承、保护继承 1393 | 1、私有继承 1394 | 使用 private 方式继承父类,公开的变成私有,其他的不变(有争议),这种继承方式防止父类的成员扩散。 1395 | 1396 | 使用 protected 方式继承父类,公开成员在子类中会变成保护的,其他不变,这种继承方式可以有效防止父类的成员扩散。 1397 | 1398 | 子类以私有或保护方式继承父类,会禁止向上造型(子类的指针或引用不能隐式转换成父类的指针或引用,要想实现多态只能以公开方式继承 1399 | 父类)。 1400 | 1401 | 三、多重继承、钻石继承、虚继承 1402 | 1、多重载继承 1403 | 在C++中一个子类可以有多个父类,在继承表中按照顺序继承多个父类中的属性和行为,并按照顺序表,调用父类的构造函数。 1404 | 按照从低到高的地址顺序排序父类,子类中会标记每个父类存储位置。 1405 | 当子类指针转换成父类的隐式指针时候,编译器会自动计算父类中的内容在子类中的位置,地址会自动进行偏移计算。 1406 | 1407 | 1408 | 2、名字冲突 1409 | 如果父类中有同名的成员,可以正常继承,但如果直接使用,会造成歧义,需要 类名::成员名 进行访问。 1410 | 1411 | 1412 | 3、钻石继承 1413 | 假如有一个类A,类B继承类A,类C也继承类A,类D继承B和C。 1414 | 一个子类继承多个父类,这些父类有一个共同的祖先,这种继承叫钻石继承。 1415 | 注意:钻石继承不会导致继承错误,但访问祖先类中的成员时每次需要使用 类名::成员名 ,重点是这种继承会造成冗余。 1416 | 1417 | 4、虚继承 virtual 1418 | 当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。 1419 | 注意:但使用虚继承时,子类中会多了一些内容(指向从祖先类继承来的成员)。 1420 | #include 1421 | #include 1422 | using namespace std; 1423 | 1424 | class A 1425 | { 1426 | public: 1427 | int a; 1428 | }; 1429 | 1430 | class B:virtual public A{}; 1431 | class C:virtual public A{}; 1432 | class D:public B,public C{}; 1433 | 1434 | int main() 1435 | { 1436 | cout<< sizeof(B)<a = 100; 1439 | A* ap = p; 1440 | A* ap1 = new C; 1441 | ap1->a = 111; 1442 | printf("%d %d\n",*(int*)ap,*(int*)ap1); 1443 | // printf("%d\n",*((int*)p+1)); 1444 | // printf("%p %p\n",p,ap); 1445 | } 1446 | 1447 | 5、构造函数 1448 | 一旦进行了虚继承祖先类的构造函数只执行一次,由孙子类直接调用,祖先类的有参构造也需要在孙子类中显示调用。 1449 | 1450 | 6、拷贝构造 1451 | 在虚拟继承(钻石)中祖先类拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动实现的拷贝构造时(深拷贝),祖先 1452 | 类中的内容也由孙子类负责拷贝,同理赋值构造也一样。 1453 | 1454 | 四、虚函数、覆盖、多态 1455 | 1、虚函数 1456 | 类的成员函数前加 virtual 这种函数就叫做虚函数。 1457 | 1458 | 2、覆盖 1459 | 子类会覆盖父类的虚函数。 1460 | 1461 | 3、多态 1462 | 当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象是谁来决定执行谁的函数,这就是多态。 1463 | #include 1464 | #include 1465 | #include 1466 | using namespace std; 1467 | 1468 | class Base 1469 | { 1470 | public: 1471 | virtual void func(void) 1472 | { 1473 | cout << "我是Base的func函数"<func(); // 并没有消失 1511 | a->func(); // 调用子类函数 1512 | p->func(); // 如果父类的函数是虚函数,调用子类函数 1513 | */ 1514 | 1515 | srand(time(NULL)); 1516 | 1517 | // 这就是多态 1518 | Base* arr[] = {new A,new B,new C}; 1519 | arr[rand()%3]->func(); 1520 | } 1521 | 1522 | 五、覆盖和多态的条件 1523 | 1、覆盖的条件 1524 | 必须是虚函数 1525 | 必须是父子类之间 1526 | 函数签名必须相同(参数列表完全一致,const属性也会影响覆盖的结果) 1527 | 返回值必须是同类型或父子类(子类的返回值要能向父类隐式转换) 1528 | 访问属性不会影响覆盖 1529 | 常函数属性也会影响覆盖 1530 | #include 1531 | #include 1532 | #include 1533 | using namespace std; 1534 | 1535 | class Base 1536 | { 1537 | public: 1538 | virtual Base* func(void) 1539 | //virtual void func(void) 1540 | { 1541 | cout << "我是Base的func函数"<func(); 1562 | } 1563 | 1564 | 2、重载、隐藏、覆盖(重写)的区别 1565 | 重载:同一作用域下的同名函数,函数签名不同(类型、个数、顺序、常函数等),构成重载关系。 1566 | 覆盖:符合一系列条件。 1567 | 隐藏:父子类之间的同名成员如果没有形成覆盖,且能通过编译,必定构成隐藏。 1568 | 3、多态的条件 1569 | 1、父子类之间有的函数有覆盖关系。 1570 | 2、父类的指针或引用指向子类的对象。 1571 | 4、在构造、析构函数中调用虚函数 1572 | 在父类的构造函数中调用虚函数,此时子类还没有创建完成(回顾构造函数的调用过程),因此只能调用父类的虚函数,而不是覆盖版本 1573 | 的虚函数。 1574 | 在父类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,而不是覆盖版本的虚函数。 1575 | #include 1576 | #include 1577 | #include 1578 | using namespace std; 1579 | 1580 | class Base 1581 | { 1582 | public: 1583 | Base(void) 1584 | { 1585 | func(); 1586 | } 1587 | 1588 | virtual void func(void) 1589 | { 1590 | cout << "我是Base的func函数"<func(); 1625 | } 1626 | 1627 | 六、纯虚函数和抽象类 1628 | 1、纯虚函数 1629 | 在虚函数的声明的后面添加=0,这种虚函数就叫做纯虚函数,可以不实现,但如果实现必须在类外(只能在父类的构造函数、析构函数中调用)。 1630 | virtual 返回值 函数名(参数) = 0; 1631 | 1632 | #include 1633 | #include 1634 | using namespace std; 1635 | 1636 | class Base 1637 | { 1638 | public: 1639 | Base(void) 1640 | { 1641 | func(); 1642 | } 1643 | // 纯虚函数 1644 | virtual void func(void) = 0; 1645 | ~Base(void) 1646 | { 1647 | func(); 1648 | } 1649 | }; 1650 | 1651 | class A:public Base 1652 | { 1653 | public: 1654 | void func(void) 1655 | { 1656 | cout << "我是纯虚函数的覆盖"< 1682 | using namespace std; 1683 | 1684 | class Base 1685 | { 1686 | public: 1687 | virtual void show(void) = 0; 1688 | }; 1689 | 1690 | class A:public Base 1691 | { 1692 | public: 1693 | void show(void) 1694 | { 1695 | cout << "我是类A的show函数" << endl; 1696 | } 1697 | }; 1698 | class B:public Base 1699 | { 1700 | public: 1701 | void show(void) 1702 | { 1703 | cout << "我是类B的show函数" << endl; 1704 | } 1705 | }; 1706 | class C:public Base 1707 | { 1708 | public: 1709 | void show(void) 1710 | { 1711 | cout << "我是类C的show函数" << endl; 1712 | } 1713 | }; 1714 | 1715 | enum ClassType{typeA,typeB,typeC}; 1716 | 1717 | // 工厂类模式 1718 | Base* creat_object(ClassType type) 1719 | { 1720 | switch(type) 1721 | { 1722 | case typeA: return new A; 1723 | case typeB: return new B; 1724 | case typeC: return new C; 1725 | default: return NULL; 1726 | } 1727 | } 1728 | 1729 | int main() 1730 | { 1731 | Base* p = creat_object(typeA); 1732 | p->show(); 1733 | } -------------------------------------------------------------------------------- /C++.cpp: -------------------------------------------------------------------------------- 1 | C与C++的区别? 2 | 一、C++介绍 3 | 本贾尼·斯特劳斯特卢普,与1979年4月份贝尔实验室的本贾尼博士在分析UNIX系统分布内核流量分析时,希望有一种有效的更加模块化的工具。 4 | 1979年10月完成了预处理器Cpre,为C增加了类机制,也就是面向对象,1983年完成了C++的第一个版本,C with classes也就是C++。 5 | C++与C的不同点: 6 | 1、C++基本兼容C的语法 7 | 2、支持面向对象的编程思想 8 | 3、支持运算符重载 9 | 4、支持泛型编程、模板 10 | 5、支持异常处理 11 | 6、类型检查严格 12 | 13 | 二、第一个C++程序 14 | 1、文件扩展名 15 | .cpp .cc .C .cxx 16 | 2、编译器 17 | g++ 大多数系统需要额外安装,Ubuntu系统下的安装命令: 18 | sudo apt-get update 19 | sudo apt-get install g++ 20 | gcc也可以继续使用,但需要增加参数 -xC++ -lstdc++ 21 | 3、头文件 22 | #include 23 | #include 可以继续使用,但C++建议使用 #include 24 | 4、输入/输出 25 | cin >> 输入数据 26 | cout << 输出数据 27 | cin/cout会自动识别类型 28 | scanf/printf可以继续使用 29 | 注意:cout和cin是类对象,而scanf/printf是标准库函数。 30 | 5、增加了名字空间 31 | std::cout 32 | using namespace std; 33 | 34 | 三、名字空间 35 | 1、什么是名字空间 36 | 在C++中经常使用多个独立开发的库来完成项目,由于库的作者或开发人员没见过面,因此命名冲突在所难免。 37 | 2、为什么需要名字空间 38 | 在项目中函数名、全局变量、结构、联合、枚举、类,非常有可能名字冲突,而名字空间就对这些命名进行逻辑空间划分(不是物理单元划分), 39 | 为了解决命名冲突,C++之父为防止命名冲突给C++设计一个名字空间的机制。 40 | 通过使用namespace XXX把库中的变量、函数、类型、结构等包含在名字空间中,形成自己的作用域,避免名字冲突。 41 | namespace xxx 42 | { 43 | 44 | }// 没有分号 45 | 注意:名字空间也是一种标识符,在同一作用域下不能重名。 46 | 47 | 3、同名的名字空间有自动合并(为了声明和定义可以分开写) 48 | 同名的名字空间中如果有重名的依然会命名冲突 49 | 50 | 4、名字空间的使用方法 51 | ::域限定符 52 | 空间名::标识符 // 使用麻烦,但是非常安全 53 | using namespace 空间名; 把空间中定义的标识符导入到当前代码中 54 | 不建议这样使用,相当于把垃圾分类后,又导入同一个垃圾车,依然会冲突 55 | 56 | 5、无名名字空间 57 | 不属于任何名字空间中的标识符,隶属于无名名字空间。 58 | 无名名字空间中的成员使用 ::标识符 进行访问。 59 | 如何访问被屏蔽的全局变量。 60 | 61 | 6、名字空间的嵌套 62 | 名字空间内部可以再定义名字空间,这种名字空间嵌套 63 | 内层的名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符。 64 | 多层的名字空间在使用时逐层分解。 65 | n1::n2::num; 66 | namespace n1 67 | { 68 | int num = 1; 69 | namespace n2 70 | { 71 | int num = 2; 72 | namespace n3 73 | { 74 | 75 | } 76 | } 77 | } 78 | 79 | 7、可以给名字空间取别名 80 | 由于名字空间可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给名字空间取别名来解决这类问题。 81 | namespace n123 = n1::n2::n3; 82 | 83 | 四、C++的结构 84 | 1、不再需要 typedef ,在定义结构变量时,可以省略struct关键字 85 | 2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针。 86 | 3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。 87 | 4、可以继承,可以设置成员的访问权限(面向对象)。 88 | 89 | 五、C++的联合 90 | 1、不再需要 typedef ,在定义结构变量时,可以省略union关键字 91 | 2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针。 92 | 3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。 93 | 94 | 六、C++的枚举 95 | 1、定义、使用方法与C语言基本一致。 96 | 2、类型检查比C语言更严格 97 | 98 | 七、C++的布尔类型 99 | 1、C++具有真的布尔类型,bool是C++中的关键字,在C语言中使用布尔类型需要导入头文件stdbool.h(在C11中bool应该是数据类型了)。 100 | 2、在C++中 true false 是关键字,而在C语言中不是。 101 | 3、在C++中 true false 是1字节,而C语言中是4字节。 102 | 103 | 八、C++的void* 104 | 1、C语言中void* 可以与任意类型指针 自动转换。 105 | 2、C++中void*不能给其他类型的指针直接赋值,必须强制类型转换,但其他类型的指针可以自动给void*赋值。 106 | 3、C++为什么这样修改void*? 107 | 为了更安全,所以C++类型检查更严格。 108 | 109 | 110 | 九、操作符别名 111 | 某些特殊语言的键没有~,&符合,国际标准化组织为一些操作符规定了别名,以便使用这些语言的键盘也能输入正确的C/C++代码。 C95和C++98以后的语言标准都支持ISO-646 112 | and && 113 | or || 114 | not ! 115 | { <% 116 | } %> 117 | # :% 118 | 119 | 十、函数重载 120 | 1、函数重载 121 | 在同一作用域下,函数名相同,参数列表不同的函数,构成重载关系。 122 | 2、重载实现的机制 123 | C++代码在编译时会把函数的参数类型添加到参数名中,借助这个方式来实现函数重载,也就是C++的函数在编译期间经历换名的过程。 124 | 因此,C++代码不能调用C函数(C语言编译器编译出的函数) 125 | 3、extern "C" {} 126 | 告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件可以与C目标文件合并生成可执行程序)。 127 | 如果C想调用C++编译出的函数,需要将C++函数的定义用extern "C"包括一下。 128 | 注意:如果两个函数名一样,一定会冲突。 129 | 4、重载和作用域 130 | 函数的重载关系发生在同一作用域下,不同作用域下的同名函数,构成隐藏关系。 131 | 5、重载解析 132 | 当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫重载解析。 133 | 实参的类型和形参的匹配情况有三种: 134 | 1、编译器找到与实参最佳的匹配函数,编译器将生成调用代码。 135 | 2、编译找不到匹配函数,编译器将给出错误信息。 136 | 3、编译器找到多个匹配函数,但没有一个最佳的,这种错误叫二义性。 137 | 在大多数情况下编译器都能立即找到一个最佳的调用版本,但如果没有,编译就会进行类型提升,这样备选函数中就可能具有多个可调用 138 | 的版本,这样就可能产生二义性错误。 139 | 6、确定存在函数的三个步骤 140 | 1)候选函数 141 | 函数调用的第一步就是确定所有可调用的函数的集合(函数名、作用域),该集合中的函数就是候选函数。 142 | 2)选择可行函数 143 | 从候选函数中选择一个或多个函数,选择的标准是参数个数相同,而且通过类型提升实参可被隐式转换为形参。 144 | 3)寻找最佳匹配 145 | 优先每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的字节数。 146 | 7、指针类型会对函数重载造成影响 147 | C++函数的形参如果是指针类型,编译时函数名中会追加Px。 148 | 149 | 十一、默认形参 150 | 1、在C++中函数的形参可以设置默认值,调用函数,如果没有提供实参数,则使用默认形参。 151 | 2、如果形参只有一部分设置了默认形参,在某个提供了默认值的参数后面,所有的参数都必须提供默认值。 152 | 3、函数的默认形参是在编译阶段确定的,因此只能使用常量、常量表达式、全局变量数据作为默认值。 153 | 提问:如果函数的声明和定义需要分开,那么默认形参设置在声明、定义,还是声明定义都需要设置? 154 | 4、默认形参会对函数重载造成影响,设置默认形参时一定要慎重。 155 | 156 | 十二、内联函数 157 | 1、普通函数调用时是生成调用指令(跳转),然后当代码执行到调用位置时跳转到函数所在的代码段执行。 158 | 2、内联函数就把函数编译好的二进制指令直接复制到函数的调用位置。 159 | 3、内联函数的优点就是提高程序的运行速度(因为没有跳转,也不需要返回),但这样会导致可执行文件增大(冗余),也就是牺牲空间来换取时间。 160 | 4、内联分为显示内联和隐式内联 161 | 显示内联:在函数前 inline(C语言C99标准也支持) 162 | 隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数。 163 | 5、宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间,所以宏函数与内联函数的区别(优缺点)? 164 | 1、宏函数不是真正的函数,只是代码替换,不会有参数压栈、出栈以及返回值,也不会检查参数类型,因此所有类型都能使用,但这样会 165 | 有安全隐患。 166 | 2、内联函数是真正的函数,被调用时会进行传参,会进行压栈、出栈,可以有返回值,并会严格检查参数类型,这样就不能通用,如果想被 167 | 多种类型调用需要重载。 168 | 6、内联适用的条件 169 | 由于内联会造成可执行文件变大,并增加内存开销,因此只有频繁调用的简单函数适合作为内联。 170 | 调用比较少的复杂函数,内联后并不显著提高性能,不足以抵消牺牲空间带来的损失,所以不适合内联。 171 | 带有递归特性和动态绑定特性的函数,无法实施内联,因此编译器会忽略声明部分的inline关键字。 172 | 173 | 十三、引用 174 | 引用就是取艺名(别名)。 175 | 1、引用的基本特性 176 | 引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号。 177 | 1、引用必须初始化,不存在空引用,但有悬空引用(变量死了,名还留着)。 178 | 2、可以引用无名对象(临时对象),但必须使用常引用。 179 | 3、引用不能更换目标 180 | 4、引用目标如果具有const属性,引用也需要具有const属性。 181 | 引用一旦完成了定义和初始化就和普通变量名一样,它就代表了目标,一经引用终身不能再引用其他目标。 182 | 2、引用型参数 183 | 引用当作函数的参数能达到指针同样的效果,但不具备指针的危险,还比指针方便。 184 | 引用可以非常简单的实现函数间共享变量的目的,而且是否使用引用由被调函数说了算。 185 | 引用当作函数的参数还能提高传递参数效率,指针至少还需要4字节内存,而引用只需要增加一条标识符与内存之间的绑定(映射)。 186 | 3、引用型返回值 187 | 不要返回局部变量的引用,会造成悬空引用。 188 | 如果返回值是一个临时值(右值),如果非要使用引用接收的话,必须使用常引用。 189 | 注意:C++中的引用时一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数)。 190 | 练习1:实现一个C++版本的swap函数。 191 | 192 | 指针和引用的相同点和不同点: 193 | 相同点:跨函数共享变量,优化传参效率,避免传参的时候调用拷贝构造 194 | 不同点:指针有自己的存储空间,借助指针可以使用堆内存,引用不行。引用取别名,指针是数据类型。指针可以为空,引用不可以为空。 195 | 指针可以不初始化,引用必须初始化。指针可以改变指向,引用不能引用其他对象(可以定义指针的指针,不能定义引用的引用。可以定义指针的 196 | 引用,不能定义引用的指针。可以定义指针的数组,但不能定义引用的数组。可以定义数组的引用)。 197 | 198 | #include 199 | using namespace std; 200 | 201 | void swap(int& a,int& b) //引用 202 | { 203 | int temp = a; 204 | a = b; 205 | b = temp; 206 | } 207 | 208 | int main() 209 | { 210 | int a=3,b=4; 211 | swap(a,b); 212 | cout< 233 | using namespace std; 234 | 235 | struct Student 236 | { 237 | Student(void) 238 | { 239 | cout<<"我是构造函数,创建对象时,我就会执行" << endl; 240 | } 241 | 242 | ~Student(void) 243 | { 244 | cout<< "我是析构函数,释放对象时,我就会执行" << endl; 245 | } 246 | }; 247 | 248 | int main() 249 | { 250 | int* p = new int; 251 | *p = 10; 252 | 253 | cout<< *p < 283 | using namespace std; 284 | 285 | int main() 286 | { 287 | int *p = NULL; 288 | try{ 289 | p = new int[~0]; 290 | } 291 | catch(std::bad_alloc& ex) 292 | { 293 | cout << "error" << endl; 294 | } 295 | } 296 | 297 | 十五、强制类型转换 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 面向过程编程: 329 | 关注是问题解决的过程步骤,算法 330 | 331 | 面向对象编程: 332 | 关注的是谁能解决问题(类),需要什么样的数据(成员变量),具备什么样的技能(成员函数)才能解决问题。 333 | 抽象:找出一个能够解决问题的“对象”(观察研究对象),找出解决所必须的数据(属性)、功能(成员函数)。 334 | 封装:把抽象的结果,归结为一个类(数据类型),然后实例化出类对象,设置对象的属性,调用对象的功能达到解决问题的目的。 335 | 继承:在解决问题前,先寻找之前的类能不能解决问题,或解决部分问题,如果可以则把旧的类继承后再次拓展,来缩短解决问题的时间,降低 336 | 解决问题的难度。 337 | 多态:对象的多种形态,外部看到一个对象发出指令,对象会根据自身情况做出独特的反应。 338 | 339 | 一、类和对象 340 | 1、通过分析对象的属性和行为设计出一个类。 341 | 2、类就是数据类型 342 | 简单类型:只能表示一个属性(变量),C/C++内建数据类型 343 | 数组类型:可以表示多个属性(变量),类型必须相同。 344 | 结构类型:可以表示多个属性(变量),但缺少行为(函数)。 345 | 类类型:即能表示属性,也能表示行为,一直复合数据类型。 346 | 3、对象就是类这种数据类型创建出的实例,相当于结构变量。 347 | class Student 348 | { 349 | 属性(成员变量); 350 | 行为(成员函数); 351 | }; 352 | 353 | Student stu; 354 | 355 | 二、类的定义与实例化 356 | 1、类的一般形式 357 | class 类名 : 继承方式 父类 358 | { 359 | public/private/protected: // 访问控制限制符 360 | 361 | 成员变量; 362 | 363 | // 构造函数 364 | 类名(形参表) 365 | { 366 | 367 | } 368 | 369 | // 析构函数 370 | ~类名(void) 371 | { 372 | 373 | } 374 | }; 375 | 2、类的访问控制限定符 376 | public:公有成员,在任何位置都可以访问 377 | private:私有成员,只能在类(自己)的成员函数中访问 378 | protected:受保护成员,只能在类(自己)和子类中访问 379 | 注意:类中的成员变量、成员函数默认是 private,结构中的成员和成员函数默认是 public。 380 | 注意:C++中类和结构的区别只有成员函数和成员变量的默认访问权限不同。 381 | 3、构造函数 382 | 1)什么是构造函数:类的同名函数就是构造函数,没有返回值。 383 | 2)什么时候调用,谁调用,调用几次? 384 | 创建类对象时会被自动调用(每创建一个类对象,就会调用一次),对象整个生命周期中一定会被 385 | 调用一次,只能被调用一次。 386 | 3)负责干什么 387 | 成员变量的初始化,分配相关资源,设置对象的初始状态。 388 | 389 | class 类名 : 继承方式 父类 390 | { 391 | // 构造函数 392 | 类名(形参表) 393 | { 394 | 395 | } 396 | }; 397 | 4、类型的创建过程 398 | 1.分配类型所需要空间,无论栈还是堆。 399 | 2.传递实参调用构造函数,完成如下任务: 400 | 1)根据继承表依次调用父类的构造函数 401 | 2)根据成员变量的顺序依次调用成员变量的构造函数。 402 | 3)执行构造函数体中的代码。 403 | 注意:执行构造函数的代码是整个构造函数的最后一步。 404 | 要保证构造函数代码所需要的一切资源和先决条件在该代码执行前已经准备充分,并得到正确的初始化。 405 | 5、对象的创建方法 406 | 1.在栈上创建:类名 对象;// 不需要括号 407 | 类名 对象(实参); 408 | 2.在堆上创建:类名* 对象指针 = new 类名; 409 | 类名* 对象指针 = new 类名(实参); 410 | 3.创建多个对象: 411 | 类名 对象 = {类名(实参),类名(实参),类名(实参)}; 412 | 类名* 对象指针 = new 类名[n]{类名(实参),类名(实参)}; 413 | 注意:通过malloc创建的类对象不能调用构造函数。 414 | 注意:通过new[]创建的对象,一定要通过delete[]释放。 415 | 6、类的声明、实现、调用 416 | 1.在头文件中声明 417 | class 类名 : 继承方式 父类 418 | { 419 | 成员变量; 420 | public: // 访问控制限制符 421 | // 构造函数 422 | 类名(形参表); 423 | // 析构函数 424 | ~类名(void); 425 | // 其他成员函数 426 | 返回值 函数名(参数列表); 427 | }; 428 | 429 | 2.源文件实现类的相关函数 430 | 返回值 类名::函数名(参数列表) 431 | { 432 | 433 | } 434 | 435 | 3.调用时只需要导入头文件,然后与类函数所在的源文件一起编译即可。 436 | 注意:如果一个类内容不多,可以考虑在头文件中完全实现。 437 | 也可以只在头文件中实现一些简单的成员函数。 438 | 注意:类中自动生成的函数,在源文件中实现时,也需要在头文件中声明。 439 | 440 | class和struct的区别? 441 | class的默认继承和访问权限是private,struct的默认继承和访问权限是public。class能做模板的参数,struct不行。 442 | 443 | 三、构造函数与初始化列表 444 | 1、构造函数可以被重载(同一个名字的函数有多个不同版本) 445 | 2、缺省构造是编译器自动生成的一个什么都不做的构造函数(唯一的作用就是避免编译错误)。 446 | 注意:当类实现一个有参构造时,缺省构造就不会再自动生成,如果有需要必须显示地写出来。 447 | 3、无参构造未必无参,当给有参构造的所有参数设置默认形参,调用这种构造函数就不需要传参。 448 | 449 | 注意:所谓的“编译器生成的某某函数”其实不是真正语法意义上的函数,而是功能意义上的函数,编译器作为可执行指令的生成者,它会直接生成 450 | 具有某项功能的二进制指令,不需要借助高级语言语义上的函数完成此任务。 451 | 452 | 注意:如果一个类是其他类的成员变量,那么一定要保证它有一个无参构造,当B的构造函数执行时会执行成员变量的无参构造,而此时类B是无 453 | 法给类A成员变量提供参数的。 454 | 4、单参构造与类型转换 455 | 如果构造函数的参数只有一个,那么Test t = n语句就不会出错,它会自动调用单参构造来达到类型转换的效果。 456 | 如果想禁止这种类型转换需要在单参构造前加 explicit 457 | 5、初始化列表 458 | 为类成员进行初始化用的。 459 | 构造函数(参数):成员1(参数1),成员2(参数2)... 460 | const int num; 461 | Test(int n):num(n) 462 | { 463 | } 464 | 通过初始化列表可以给类成员变量传递参数,以此调用类成员的有参构造。 465 | 初始化列表也可以给 const 成员、引用成员进行初始化。 466 | #include 467 | using namespace std; 468 | 469 | class A 470 | { 471 | public: 472 | int num; 473 | A(int _num) 474 | { 475 | num = _num; 476 | cout<<"我A的有参构造"< 507 | using namespace std; 508 | 509 | class A 510 | { 511 | public: 512 | A(int n) 513 | { 514 | cout<<"A"< 609 | #include 610 | using namespace std; 611 | 612 | class User 613 | { 614 | char name[20]; 615 | char pass[7]; 616 | public: 617 | User(const char* name,const char* pass) 618 | { 619 | strcpy(this->name,name); 620 | strcpy(this->pass,pass); 621 | //show(); 622 | } 623 | 624 | User& func(void) 625 | { 626 | return *this; 627 | } 628 | 629 | void show(void)//隐藏this指针 630 | { 631 | cout<< name << " "<< pass < 675 | #include 676 | #include 677 | using namespace std; 678 | 679 | class A 680 | { 681 | public: 682 | A(void) 683 | { 684 | cout << "A 's 构造" <name = new char[strlen(name)+1]; 716 | strcpy(this->name,name); 717 | this->pass = new char[strlen(pass)+1]; 718 | strcpy(this->pass,pass); 719 | cout<< "构造"< 父类构造-> 成员构造-> 自己构造 753 | 父类构造:按照继承表从左到右依次构造。 754 | 成员构造:按照声明顺序从上至下依次构造。 755 | 释放:自己析构-> 成员析构-> 父类析构-> 释放内存(对象) 756 | 成员析构:按照声明顺序从下到上依次构造。 757 | 父类析构:按照继承表从右到左依次构造。 758 | 759 | 四、拷贝构造 760 | 拷贝构造又称为复制构造,是一种特殊的构造函数,它是使用一个现有的旧对象构造一个新的对象时调用的函数,只有一个引用型的参数(对象本身)。 761 | 类名(类& ) 762 | { 763 | 764 | } 765 | 拷贝构造的参数应该加 const 保护,但编译器并没有强行限制。 766 | 编译器会自己生成一个拷贝构造函数,它负责把旧对象中的所有数据拷贝给新创建的对象。 767 | 768 | 深拷贝与浅拷贝的区别: 769 | 如果类成员有指针,浅拷贝只拷贝指针变量的值,而深拷贝指针变量所指向的目标。 770 | 什么情况下需要实现拷贝构造: 771 | 当类成员中有指针成员,此时默认的拷贝构造(浅拷贝)就无法完成任务,需要自己动手实现拷贝构造(深拷贝)。 772 | 什么情况下会调用拷贝构造: 773 | 1、使用旧对象给新对象赋值时 774 | User user1 = user; 775 | 2、使用对象当作函数的参数,当调用函数时,就会一起调用拷贝构造。 776 | #include 777 | #include 778 | 779 | using namespace std; 780 | 781 | class User 782 | { 783 | char* name; 784 | char pass[7]; 785 | int id; 786 | public: 787 | User(const char* name,const char* pass) 788 | { 789 | this->name = new char[strlen(name)+1]; 790 | strcpy(this->name,name); 791 | strcpy(this->pass,pass); 792 | } 793 | void show(void) 794 | { 795 | cout< 841 | #include 842 | 843 | using namespace std; 844 | 845 | class User 846 | { 847 | char* name; 848 | char pass[7]; 849 | public: 850 | User(const char* name,const char* pass) 851 | { 852 | this->name = new char[strlen(name)+1]; 853 | strcpy(this->name,name); 854 | strcpy(this->pass,pass); 855 | } 856 | void show(void) 857 | { 858 | cout<) 921 | 922 | 七、静态成员 923 | 类成员一旦被 static 修饰就会变成静态成员,而是单独一份存储在bss或data内存段中,所有的类对象共享(静态成员属于类,而不属于某个对象)。 924 | 静态成员在类内声明,但必须在类外定义、初始化。与成员函数一样需要加“类名::”限定符表示它属于哪个类,但不需要再额外增加 static 925 | 926 | 成员函数也可以被static修饰,这种函数叫静态成员函数,这种成员没有this指针,因此在静态函数中不能直接访问类的成员,但可以直接访问 927 | 静态成员,但可以直接访问静态成员变量、静态成员函数。 928 | 静态成员变量、函数依然受访问控制限定符的影响。 929 | 因为在代码编译完成后,静态成员已经定义完成(有了存储空间),一次可以不用活类对象而直接调用,类名::静态成员名 930 | 931 | 静态成员变量可以被当做全局变量来使用(访问限定符必须是public),静态成员函数可以当作类的接口,实现对类的管理。 932 | 933 | 八、单例模式 934 | 什么是单例模式,只能创建出一个类对象(只有一实际的实例)的叫单例模式。 935 | 单例模式的应用场景: 936 | Windows系统的任务管理器 937 | Linux/Unix系统的日志系统 938 | 网站的访问计数器 939 | 服务端程序的连接池、线程池、数据池 940 | 获取单一对象的方法: 941 | 1、定义全局(C语言),但不受控制,防君子不能防小人。 942 | 2、专门写一个类,把类的构造函数设置私有,借助静态成员函数提供一个接口,以此来获取唯一的实例。 943 | C++如何实现单例: 944 | 1、禁止类的外部创建类对象:构造函数设置私有 945 | 2、类自己维护一个唯一的实例:使用静态指针指向 946 | 3、提供一个获取实例的方法:静态成员函数获取静态指针 947 | 948 | 饿汉模式: 949 | 将单例类的唯一实例对象定义为成员变量,当程序开始运行时,实例对象就已经创建完成 950 | 优点:加载进程时,静态创建单例对象,线程安全。 951 | 缺点:无论使用与否,总要创建,浪费内存。 952 | 懒汉模式: 953 | 用静态成员指针来指向单例类的唯一实例对象,只有真正调用获取实例的静态接口时,实例对象才被创建。 954 | 优点:什么时候用什么时候创建,节约内存。 955 | 缺点:在第一次调用获取实例对象的静态接口时,才真正创建,如果在多线程操作情况下有可能被创建出多个实例对象(虽然可能性很低), 956 | 存在线程不安全问题。 957 | 958 | 总结:C语言与C++有哪些不同点 959 | 内存管理 malloc/free new/delete 960 | static 961 | const 962 | void* 963 | 字符串:string系列函数 string类 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 一、操作符函数重载 994 | 什么是操作符函数:在C++中针对类类型的对象的运算符,由于它们肯定不支持真正的运算操作,因此编译器会将它们翻译成函数,这种就叫做 995 | 操作符函数(运算符函数)。 996 | 编译器把运算翻译成运算符函数,可以针对自定义的类类型设计它独有的运算功能。 997 | 其实各种运算符已经具备一些功能,再次实现它的就是叫作运算符重载。 998 | 999 | 双目运算符: 1000 | a+b 1001 | 成员函数 1002 | a.operator+(b); 1003 | 全局函数 1004 | operator+(a,b); 1005 | 单目运算符: 1006 | !a 1007 | 成员函数 1008 | a.operator!(void); 1009 | 全局函数 1010 | operator!(a); 1011 | 1012 | 二、双目操作符函数重载 1013 | 成员函数: 1014 | const 类对象 operator#(const 类& that) const 1015 | { 1016 | return 类(参数#参数); 1017 | } 1018 | 注意:双目录运算符的运算结果是个右值,返回值应该加 const ,然后为了const对象能够调用,参数应写const,函数也应该具备const属性。 1019 | 1020 | 全局函数: 1021 | const 类 operator#(const 类& a,const 类& b) 1022 | { 1023 | 1024 | } 1025 | 注意:全局函数不是成员函数,可能会需要访问到类的私有成员,解决这种问题可以把函数声明为类的友元函数(友元不是成员)。 1026 | 友元:在类的外部想访问类的私有成员(public/protected/private)时,需要把所在的函数声明为友元,但是友元只是朋友,因此它只有访 1027 | 问权,没有实际的拥有权(其根本原因是它没有this指针)。 1028 | 友元声明:把函数的声明写一份到类中,然后在声明前加上friend 关键字。使用友元即可把操作符函数定义为全局的,也可以确保类的封装性。 1029 | 注意:友元函数与成员函数不会构成重载关系,因此它们不在同一个作用域内。 1030 | 1031 | 三、赋值类型的双目操作符 1032 | 成员 1033 | 类 operator#(void) 1034 | { 1035 | 1036 | } 1037 | 全局 1038 | 类 operator#(const 类& that) 1039 | { 1040 | 1041 | } 1042 | 1、获取单参构造成赋值运算的调用方式。 1043 | String str = "xxx"; // 会调用单参构造,而不调用赋值运算符 1044 | str = "hhh"; 1045 | 1046 | 2、左操作数据不能具有const属性 1047 | 1.成员函数不能是常函数 1048 | 2.全局函数第一个参数不能有const属性 1049 | 3、返回值应该都(成员/全局)具备const属性 1050 | 1051 | 四、单目操作符函数重载 1052 | -,~,!,&,*,->,++,-- 1053 | 成员 1054 | const 类 operator#(void) const 1055 | { 1056 | 1057 | } 1058 | 全局 1059 | const 类 operator#(const 类& that) 1060 | { 1061 | 1062 | } 1063 | 前++/-- 1064 | 类& operator#(void) 1065 | { 1066 | 1067 | } 1068 | 类& operator#(类& that) 1069 | { 1070 | 1071 | } 1072 | 后++/--(哑元) 1073 | const 类& operator#(void) 1074 | { 1075 | 1076 | } 1077 | const 类& operator#(类& that,int) 1078 | { 1079 | 1080 | } 1081 | 1082 | 五、输入输出操作符重载 1083 | cout 是 ostream 类型的对象,cin 是 istream 类型的对象。 1084 | 如果<>运算实现为成员函数,那么调用者应该是ostream/istream,而我们无权增加标准库的代码,因此输入/输出运算符只能定义为全局函数。 1085 | 1086 | ostream& operator<<(ostream& os,const 类& n) 1087 | { 1088 | 1089 | } 1090 | 1091 | istream& operator>>(istream& os,类& n) 1092 | { 1093 | 1094 | } 1095 | 1096 | 注意:在输入输出过程中,cin/cout会记录错误标志,因此不能加const属性。 1097 | 1098 | 六、特殊操作符的重载(笔试面试比较重要) 1099 | 1、下标操作符 [],常用于在容器类型中以下标方式获取元素。 1100 | 类型& operator[](int i) 1101 | { 1102 | 1103 | } 1104 | 2、函数操作符(),一个类如果重载函数操作符,那么它的对象就可以像函数一样使用,参数的个数、返回值类型,可以不确定,它是唯一一个 1105 | 可以参数有缺省参数的操作符。 1106 | #include 1107 | #include 1108 | using namespace std; 1109 | 1110 | class Array 1111 | { 1112 | int* arr; 1113 | size_t len; 1114 | public: 1115 | Array(size_t len):len(len) 1116 | { 1117 | arr = new int[len]; 1118 | } 1119 | 1120 | void operator()(void) 1121 | { 1122 | cout<<"emmm"<= len) 1128 | { 1129 | cout<<"下标错误"< 1149 | 如果一个类重载了*和->,那么它的对象就可以像指针一样使用。 1150 | 所谓的智能指针就是一种类对象,它支持解引用和成员访问操作符。 1151 | 4、智能指针 1152 | 常规指针的缺点: 1153 | 当一个常规指针离开它的作用域时,只有该指针所占用的空间会被释放,而它指向的内存空间能否被释放就不一定了,在一些特殊情 1154 | 况(人为、业务逻辑特殊)free或delete没有执行,就会形成内存泄漏。 1155 | 智能指针的优点: 1156 | 智能指针是一个封装了常规指针的类类型对象,当它离开作用域时,它的析构函数会自动执行,它的析构函数会负责释放常规指针所 1157 | 指向的动态内存(以正确方式创建的智能指针,它的析构函数才会正确执行)。 1158 | 智能指针和常规指针的相同点:都支持*和->运算。 1159 | 智能指针和常规指针的不同点: 1160 | 任何时候,一个对象只能使用一个智能指针来指向,而常规指针可以指向多次。 1161 | 只能指针的赋值操作需要经过拷贝构造和赋值构造特殊处理(深拷贝)。 1162 | #include 1163 | using namespace std; 1164 | 1165 | class Int 1166 | { 1167 | public: 1168 | int val; 1169 | Int(int val=0):val(val){ } 1170 | 1171 | void set_val(int val) 1172 | { 1173 | this->val = val; 1174 | } 1175 | int get_val(void) 1176 | { 1177 | return val; 1178 | } 1179 | 1180 | Int& operator=(const int val) 1181 | { 1182 | this->val = val; 1183 | return *this; 1184 | } 1185 | 1186 | ~Int(void) 1187 | { 1188 | cout<<"我是Int的析构函数"<>(ostream& os,Int& n); 1192 | }; 1193 | ostream& operator<<(ostream& os,Int& n) 1194 | { 1195 | return os< 1224 | 用法:auto_ptr<指向的类型> 指针变量名(对象的地址) 1225 | auto_ptr的局限性: 1226 | 不要使用 auto_ptr 对象保存指向动态、静态分配数组的指针。 1227 | 不能跨作用域使用,一旦离开作用域指针变量会释放它指向的对象也会释放。 1228 | 不能放入标准容器。 1229 | 不能指向对象数组。 1230 | #include 1231 | #include 1232 | using namespace std; 1233 | 1234 | class A 1235 | { 1236 | public: 1237 | A(void) 1238 | { 1239 | cout<<"构造"< ptr(new A); 1257 | (*ptr).show(); 1258 | } 1259 | 1260 | 5、new/delete/new[]/delete[]运算符重载 1261 | 1.C++缺省的堆内存管理器速度较慢,重载new/delete底层使用malloc/free可以提高运行速度。 1262 | 2.new在失败会产生异常,而每次使用new时为了安全都应该进行异常捕获,而重载new操作符只需要在操作符函数中进行一次错误处理即可。 1263 | 3.在一些占字节数比较小的类,频繁使用new,可能会产生大量的内存碎片,而重载new操作符后,可以适当的扩大每次申请的字节数,减 1264 | 少内存碎片产生的机率。 1265 | 4.重载 new/delete 可以记录堆内存使用的信息 1266 | 5.重载 delete 可以检查到释放内存失败时的信息,检查到内存泄漏。 1267 | 1268 | 七、重载操作符的限制 1269 | 1、不能重载的操作符 1270 | 域限定符 :: 1271 | 直接成员访问操作符 . 1272 | 三目操作符 ?: 1273 | 字节长度操作符 sizeof 1274 | 类型信息操作符 typeid 1275 | 2、重载操作符不能修改操作符的优先级 1276 | 3、无法重载所有基本类型的操作符运算 1277 | 4、不能修改操作符的参数个数 1278 | 5、不能发明新的操作符 1279 | 1280 | 关于操作符重载的建议: 1281 | 1、在重载操作符时要根据操作符实际的功能和意义来确定具体参数,返回值,是否具有const属性,返回值是否是引用或者临时对象。 1282 | 2、重载操作符要符合情理(要有意义),要以实际用途为前提。 1283 | 3、重载操作符的意义是为了让对象的操作更简单、方便,提高代码的可读性,而不是为了炫技。 1284 | 4、重载操作符要与默认的操作符的功能、运算规则一致,不要出现反人类的操作。 1285 | #define ture 0 1286 | #define false 1 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 一、类的继承 1323 | 1、共性与个性 1324 | 表达不同类型事物之间公有的属性和行为。 1325 | 个性用于刻画每种事物特有的属性和行为。 1326 | 2、共性表示为父类(基类),个性表示为子类(派生类)。 1327 | 子类继承自父类 1328 | 基类派生出子类 1329 | 1330 | 二、继承的基本语法 1331 | 1、继承表 1332 | 一个子类可以同时继承零到多个父类,每个父类的继承方式可以相同也可以不同。 1333 | class 子类:继承方式1 父类1,继承方式2 父类2,... 1334 | { 1335 | 1336 | } 1337 | 2、继承方式 1338 | public 公有继承:父类的特性可通过子类向外扩展。 1339 | private 私有继承:父类的特性只能为子类所有。 1340 | protected 保护继承:父类的特性只能在继承链内扩展。 1341 | 1342 | 三、继承的基本特点 1343 | 1、公共特点(所有继承都有的特点) 1344 | 子类对象可以当作父类对象使用,子类对象与父类没有本质上的区别。 1345 | 子类的逻辑空间小于父类,但它的物理空间要大于等于父类。 1346 | 子类对象 IS A 父类对象 1347 | 2、向上和向下转换(造型) 1348 | 从子类到父类:子类的指针或引用可以隐式转换成父类的指针或引用,这是一种缩小类型的转换,对于编译器来说是安全的。 1349 | 从父类到子类:父类的指针或引用不可以转换成子类的指针或引用,这是一种扩大类型的转换,在编译器看来是危险的。(子类的指针指 1350 | 向父类的对象,不安全) 1351 | 编译器仅仅是检查指针或引用的数据类型,而对实际引用的目标对象不关心(构成多态的基础)。 1352 | 类型一致:父类的指针或引用实际的目标类型是否需要转换成实际的指针或引用由程序自己决定。 1353 | 3、子类会继承父类的所有成员(公有,私有,保护) 1354 | 4、子类会隐藏父类的同名成员 1355 | 1.可以通过域限定符 父类::隐藏成员 进行访问父类中的隐藏成员 1356 | 2.可以使用父类的指针或引用来指向子类对象,然后访问父类中的隐藏成员。 1357 | 5、虽然子类继承所有父类中的成员,但不能访问父类中的私有成员。 1358 | 1359 | 四、继承方式影响访问控制 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 一、子类的构造、析构、拷贝 1395 | 1、子类的构造在执行它的构造函数前会根据继承表的顺序执行父类的构造函数。 1396 | 默认执行父类的无参构造 1397 | 显示调用有参构造,在子类的构造函数后,初始化列表中显示调用父类的有参构造函数。 1398 | 2、子类在它的析构执行完后,会根据继承表的顺序,逆顺序执行父类的析构函数。 1399 | 注意:父类的指针可以指向子类对象,当通过父类指针释放对象时,只会调用父类的析构函数,而这种析构方式有可能造成内存泄漏。 1400 | 3、当使用子类对象来初始化新的子类对象时,会自动调用子类缺省的拷贝构造函数,并且会先调用父类缺省的拷贝构造函数。 1401 | 如果子类中实现的拷贝构造,需要显式调用父类拷贝构造,否则就会调用无参构造。 1402 | 1403 | 二、私有继承、保护继承 1404 | 1、私有继承 1405 | 使用 private 方式继承父类,公开的变成私有,其他的不变(有争议),这种继承方式防止父类的成员扩散。 1406 | 1407 | 使用 protected 方式继承父类,公开成员在子类中会变成保护的,其他不变,这种继承方式可以有效防止父类的成员扩散。 1408 | 1409 | 子类以私有或保护方式继承父类,会禁止向上造型(子类的指针或引用不能隐式转换成父类的指针或引用,要想实现多态只能以公开方式继承 1410 | 父类)。 1411 | 1412 | 三、多重继承、钻石继承、虚继承 1413 | 1、多重载继承 1414 | 在C++中一个子类可以有多个父类,在继承表中按照顺序继承多个父类中的属性和行为,并按照顺序表,调用父类的构造函数。 1415 | 按照从低到高的地址顺序排序父类,子类中会标记每个父类存储位置。 1416 | 当子类指针转换成父类的隐式指针时候,编译器会自动计算父类中的内容在子类中的位置,地址会自动进行偏移计算。 1417 | 1418 | 1419 | 2、名字冲突 1420 | 如果父类中有同名的成员,可以正常继承,但如果直接使用,会造成歧义,需要 类名::成员名 进行访问。 1421 | 1422 | 1423 | 3、钻石继承 1424 | 假如有一个类A,类B继承类A,类C也继承类A,类D继承B和C。 1425 | 一个子类继承多个父类,这些父类有一个共同的祖先,这种继承叫钻石继承。 1426 | 注意:钻石继承不会导致继承错误,但访问祖先类中的成员时每次需要使用 类名::成员名 ,重点是这种继承会造成冗余。 1427 | 1428 | 4、虚继承 virtual 1429 | 当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。 1430 | 注意:但使用虚继承时,子类中会多了一些内容(指向从祖先类继承来的成员)。 1431 | #include 1432 | #include 1433 | using namespace std; 1434 | 1435 | class A 1436 | { 1437 | public: 1438 | int a; 1439 | }; 1440 | 1441 | class B:virtual public A{}; 1442 | class C:virtual public A{}; 1443 | class D:public B,public C{}; 1444 | 1445 | int main() 1446 | { 1447 | cout<< sizeof(B)<a = 100; 1450 | A* ap = p; 1451 | A* ap1 = new C; 1452 | ap1->a = 111; 1453 | printf("%d %d\n",*(int*)ap,*(int*)ap1); 1454 | // printf("%d\n",*((int*)p+1)); 1455 | // printf("%p %p\n",p,ap); 1456 | } 1457 | 1458 | 5、构造函数 1459 | 一旦进行了虚继承祖先类的构造函数只执行一次,由孙子类直接调用,祖先类的有参构造也需要在孙子类中显示调用。 1460 | 1461 | 6、拷贝构造 1462 | 在虚拟继承(钻石)中祖先类拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动实现的拷贝构造时(深拷贝),祖先 1463 | 类中的内容也由孙子类负责拷贝,同理赋值构造也一样。 1464 | 1465 | 四、虚函数、覆盖、多态 1466 | 1、虚函数 1467 | 类的成员函数前加 virtual 这种函数就叫做虚函数。 1468 | 1469 | 2、覆盖 1470 | 子类会覆盖父类的虚函数。 1471 | 1472 | 3、多态 1473 | 当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象是谁来决定执行谁的函数,这就是多态。 1474 | #include 1475 | #include 1476 | #include 1477 | using namespace std; 1478 | 1479 | class Base 1480 | { 1481 | public: 1482 | virtual void func(void) 1483 | { 1484 | cout << "我是Base的func函数"<func(); // 并没有消失 1522 | a->func(); // 调用子类函数 1523 | p->func(); // 如果父类的函数是虚函数,调用子类函数 1524 | */ 1525 | 1526 | srand(time(NULL)); 1527 | 1528 | // 这就是多态 1529 | Base* arr[] = {new A,new B,new C}; 1530 | arr[rand()%3]->func(); 1531 | } 1532 | 1533 | 五、覆盖和多态的条件 1534 | 1、覆盖的条件 1535 | 必须是虚函数 1536 | 必须是父子类之间 1537 | 函数签名必须相同(参数列表完全一致,const属性也会影响覆盖的结果) 1538 | 返回值必须是同类型或父子类(子类的返回值要能向父类隐式转换) 1539 | 访问属性不会影响覆盖 1540 | 常函数属性也会影响覆盖 1541 | #include 1542 | #include 1543 | #include 1544 | using namespace std; 1545 | 1546 | class Base 1547 | { 1548 | public: 1549 | virtual Base* func(void) 1550 | //virtual void func(void) 1551 | { 1552 | cout << "我是Base的func函数"<func(); 1573 | } 1574 | 1575 | 2、重载、隐藏、覆盖(重写)的区别 1576 | 重载:同一作用域下的同名函数,函数签名不同(类型、个数、顺序、常函数等),构成重载关系。 1577 | 覆盖: 1578 | 必须是虚函数 1579 | 必须是父子类之间 1580 | 函数签名必须相同(参数列表完全一致,const属性也会影响覆盖的结果) 1581 | 返回值必须是同类型或父子类(子类的返回值要能向父类隐式转换) 1582 | 访问属性不会影响覆盖 1583 | 常函数属性也会影响覆盖 1584 | 隐藏:父子类之间的同名成员如果没有形成覆盖,且能通过编译,必定构成隐藏。 1585 | 3、多态的条件 1586 | 1、父子类之间有的函数有覆盖关系。 1587 | 2、父类的指针或引用指向子类的对象。 1588 | 4、在构造、析构函数中调用虚函数 1589 | 在父类的构造函数中调用虚函数,此时子类还没有创建完成(回顾构造函数的调用过程),因此只能调用父类的虚函数,而不是覆盖版本 1590 | 的虚函数。 1591 | 在父类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,而不是覆盖版本的虚函数。 1592 | #include 1593 | #include 1594 | #include 1595 | using namespace std; 1596 | 1597 | class Base 1598 | { 1599 | public: 1600 | Base(void) 1601 | { 1602 | func(); 1603 | } 1604 | 1605 | virtual void func(void) 1606 | { 1607 | cout << "我是Base的func函数"<func(); 1642 | } 1643 | 1644 | 六、纯虚函数和抽象类 1645 | 1、纯虚函数 1646 | 在虚函数的声明的后面添加=0,这种虚函数就叫做纯虚函数,可以不实现,但如果实现必须在类外(只能在父类的构造函数、析构函数中调用)。 1647 | virtual 返回值 函数名(参数) = 0; 1648 | 1649 | #include 1650 | #include 1651 | using namespace std; 1652 | 1653 | class Base 1654 | { 1655 | public: 1656 | Base(void) 1657 | { 1658 | func(); 1659 | } 1660 | // 纯虚函数 1661 | virtual void func(void) = 0; 1662 | ~Base(void) 1663 | { 1664 | func(); 1665 | } 1666 | }; 1667 | 1668 | class A:public Base 1669 | { 1670 | public: 1671 | void func(void) 1672 | { 1673 | cout << "我是纯虚函数的覆盖"< 1699 | using namespace std; 1700 | 1701 | class Base 1702 | { 1703 | public: 1704 | virtual void show(void) = 0; 1705 | }; 1706 | 1707 | class A:public Base 1708 | { 1709 | public: 1710 | void show(void) 1711 | { 1712 | cout << "我是类A的show函数" << endl; 1713 | } 1714 | }; 1715 | class B:public Base 1716 | { 1717 | public: 1718 | void show(void) 1719 | { 1720 | cout << "我是类B的show函数" << endl; 1721 | } 1722 | }; 1723 | class C:public Base 1724 | { 1725 | public: 1726 | void show(void) 1727 | { 1728 | cout << "我是类C的show函数" << endl; 1729 | } 1730 | }; 1731 | 1732 | enum ClassType{typeA,typeB,typeC}; 1733 | 1734 | // 工厂类模式 1735 | Base* creat_object(ClassType type) 1736 | { 1737 | switch(type) 1738 | { 1739 | case typeA: return new A; 1740 | case typeB: return new B; 1741 | case typeC: return new C; 1742 | default: return NULL; 1743 | } 1744 | } 1745 | 1746 | int main() 1747 | { 1748 | Base* p = creat_object(typeA); 1749 | p->show(); 1750 | } 1751 | 1752 | 1753 | 1754 | 1755 | 1756 | 1757 | 1758 | 1759 | 1760 | 1761 | 1762 | 1763 | 1764 | 1765 | 1766 | 1767 | 1768 | 1769 | 1770 | 1771 | 1772 | 1773 | 1774 | 1775 | 1776 | 1777 | 1778 | 1779 | 1780 | 1781 | 1782 | 1783 | 1784 | 一、虚函数表 1785 | 什么是虚函数表,在C++的类中,一旦成员函数中有虚函数,这个类中就会多一个虚函数表指针,这个指针指向一个虚函数表,表里面记录了 1786 | 这个类中所有的虚函数,当这个类被继承,它的子类中也会有一个虚函数表(不管子类中有没有虚函数),如果子类的成员函数中有函数签名与父 1787 | 类的虚函数一样,就会用子类中的函数替换它在虚函数表中的位置,这样就达到了覆盖的效果。 1788 | 当通过类指针或引用调用函数时,会根据对象中实际的虚函数表记录来调用函数,这样就达到了多态的效果。 1789 | 多态类中的虚函数表建立在编译阶段。 1790 | 1791 | 二、虚析构 1792 | 当使用delete释放一个父类指针时,不管实际指向的对象是子类还是父类都只会调用父类的析构函数(多态肯定会出现的问题)。 1793 | 如果子类的析构函数有需要负责释放的内存,就会造成内存泄漏。 1794 | 为了解决这个问题,可以把父类的析构函数设置为虚函数,析构函数进行覆盖时不会比较函数名。 1795 | 1796 | 当父类的析构函数为虚函数时,通过父类指针或引用释放子类对象时,会自动调用子类的析构函数,子类的析构函数执行完成后也会调用父类 1797 | 的析构函数。 1798 | 注意:析构函数可以是虚函数,但构造函数不行 1799 | 1800 | 三、强制类型转换 1801 | 注意:C++中为了兼容C语言,(目标类型)源类型 依然可以继续使用,但C语言的强制类型转换安全性差,因此建议使用C++中的强制类型转换。 1802 | 注意:C++之父认为如果代码设计的完善,根本不需要用到强制类型转换,而C++的强制类型转换之所以设计的很复杂,是为了让程序员多关注 1803 | 代码本身的设计,尽量少使用。 1804 | 1805 | C++中的强制类型转换保证没有很大安全隐患。 1806 | 1807 | static_cast<目标类型>(源类型) 编译器会对源类型和目标类型做兼容性检查,不通过则报错。 1808 | dynamic_cast<目标类型>(源类型) 编译器会对源类型和目标类是否同为指针或引用,并且存在多态型的继承关系。 1809 | const_cast<目标类型>(源类型) 编译器会对源类型和目标类检查,是否同为指针或引用,除了常属性外其他必须完全相同,否则报错。 1810 | reinterpret_cast<目标类型>(源类型) 编译器会对源类型和目标类是否为指针或整数进行检查,也就是说把整数转换成指针或把指针转换 1811 | 为整数。 1812 | 1813 | 拓展: 1814 | 静态编译:指针或引用的目标是确定的,在编译时期就确定了所有的类型检查、函数调用。 1815 | 动态编译:指针或引用的目标是不确定的(多态),只有在函数调用的时候才确定具体是哪一个子类。 1816 | 1817 | 1818 | 四、I/O流 1819 | I/O流的打开模式: 1820 | ios::in 以读权限打开文件,不存在则失败,存在不清空 1821 | ios::out 以写权限打开文件,不存在则创建,存在则清空 1822 | ios::app 打开文件用于追加,不存在则创建,存在不清空 1823 | ios::binary 以二进制模式进行读写 1824 | ios::ate 打开时定位到文件末尾 1825 | ios::trunc 打开文件时清空 1826 | fstream/ifstream/ofstream 类用于进行文件操作。 1827 | 构造函数或成员函数 open 用于打开文件 1828 | good成员函数检查流是否可用 1829 | eof成员函数用于输入流是否结束 1830 | 1831 | >> 操作符用于从文件中读取数据到变量 1832 | << 操作符用于输出数据到文件 1833 | 1834 | IO流有一系列格式化控制函数,类似:左对齐、右对齐、宽度、填充、小数点位数。 1835 | #include 1836 | #include 1837 | using namespace std; 1838 | 1839 | int main() 1840 | { 1841 | fstream fsi("test.txt",ios::in); 1842 | //fs.open("test.txt",ios::in); 1843 | if(!fsi.good()) 1844 | { 1845 | cout <<"打开失败"<> str; //读到空格或换行就停止 1856 | fsi >> num >> s1 >> s2 >> s3; 1857 | cout<> arr[i]; 1865 | if(arr[i].size() == 0) 1866 | { 1867 | break; 1868 | } 1869 | i++; 1870 | } 1871 | 1872 | for(int j=0; j 1898 | #include 1899 | using namespace std; 1900 | int main() 1901 | { 1902 | fstream fs("test.txt",ios::in); 1903 | if(!fs.good()) 1904 | { 1905 | cout << "文件打开失败" << endl; 1906 | return -1; 1907 | } 1908 | // 调整文件的位置指针到末尾 1909 | fs.seekp(0,ios::end); 1910 | cout << "文件的字节数:" << fs.tellp() << endl; 1911 | fs.close(); 1912 | } 1913 | 1914 | 练习:使用C++标准IO,实现带覆盖检查的cp命令。 1915 | ./cp src dest 1916 | 1917 | #include 1918 | #include 1919 | using namespace std; 1920 | 1921 | int main(int argc,char* argv[])// 写的有点问题0.0 1922 | { 1923 | if(argc != 3) 1924 | { 1925 | cout << "命令错误" << endl; 1926 | } 1927 | // 读写 1928 | fstream fi(argv[1],ios::in); 1929 | fstream fo(argv[2],ios::out); 1930 | if(!fi.good()) 1931 | { 1932 | cout << "源文件不存在" << endl; 1933 | } 1934 | cout << "是否要覆盖目标文件,y/n" << endl; 1935 | 1936 | while(1) 1937 | { 1938 | string a; 1939 | cin >> a; 1940 | if(a == "y") 1941 | { 1942 | break; 1943 | } 1944 | 1945 | else if(a == "n") 1946 | { 1947 | return 0; 1948 | } 1949 | 1950 | else 1951 | { 1952 | cout << "指令错误" << endl; 1953 | continue; 1954 | } 1955 | } 1956 | 1957 | while(1) 1958 | { 1959 | string str; 1960 | fi >> str; 1961 | if(str.size() == 0) 1962 | { 1963 | break; 1964 | } 1965 | fo << str <<" "; // 文件末尾多个空格,需要删除,并且没有换行功能 1966 | } 1967 | } 1968 | 1969 | 五、类型信息 typeid 1970 | 用于获取数据的类型信息。 1971 | name成员函数,可以获取类型的名字,内建类型名字使用缩写。 1972 | 同时还支持 == != 用来比较是否是同一种类型。 1973 | 1974 | 如果用于判断父子类的指针或引用,它不能准确判断出实际的对象类型。但可以判断出具有多态继承关系的父子类的指针或引用,它的实际对象。 1975 | #include 1976 | #include 1977 | using namespace std; 1978 | 1979 | class Base 1980 | { 1981 | public: 1982 | virtual ~Base(void) 1983 | { 1984 | } 1985 | }; 1986 | 1987 | class Test:public Base 1988 | { 1989 | 1990 | }; 1991 | 1992 | int main() 1993 | { 1994 | Base b; 1995 | Test t; 1996 | cout << typeid(b).name() << endl; 1997 | cout << (typeid(t) == typeid(b)) << endl; 1998 | 1999 | cout << endl; 2000 | Base* p = new Test; 2001 | cout << (typeid(*p) == typeid(Test)) << endl; 2002 | cout << (typeid(p) == typeid(Test*)) << endl; 2003 | 2004 | } 2005 | 2006 | 扩展: 2007 | sudo find / -name filename 2008 | sudo find / | grep "std" 2009 | grep 'Base' * 当前目录查找包含此字符的文件 2010 | grep -r 'Base' * 当前目录及所有子级目录,查找包含此字符的文件 2011 | grep -r 'Base' * dir 指定目录下及所有子级目录,查找包含此字符的文件 2012 | 2013 | 2014 | 五、异常处理 2015 | 抛异常 2016 | throw 数据 2017 | 抛异常对象 2018 | 抛基本类型 2019 | 注意:不能抛出局部对象的指针或引用(构造函数和析构函数不能抛出异常)。 2020 | 注意:如果异常没有被捕获处理,程序就会停止。 2021 | 捕获异常 2022 | try{ 2023 | 可以抛出异常的代码 2024 | } 2025 | catch(类型 变量名) // 根据数据类型进行捕获 2026 | { 2027 | 处理异常,如果无法处理可以继续抛出异常 2028 | } 2029 | 注意:捕获异常的顺序是自上而下的,而不是最精准的匹配,针对子类异常捕获时要放在父类的前面。 2030 | 2031 | 函数的异常声明: 2032 | 返回值类型 函数名(参数列表)throw(类型1,类型2,...) 2033 | 注意:如果不写异常声明表示什么类型的异常都可能抛出。 2034 | 注意:如果写了异常声明表示只抛出某些类型的异常,一旦超出异常声明的范围,程序会直接停止,无法捕获。 2035 | 注意:throw() 表示什么类型都不会抛出 2036 | 2037 | 2038 | 设计异常类: 2039 | class Error 2040 | { 2041 | int errno; 2042 | char errmsg[255]; 2043 | public: 2044 | Error(int errno = -1,const char* msg = "未知错误") 2045 | { 2046 | this->errno = errno; 2047 | strcpy(errmsg,msg); 2048 | } 2049 | int getError(void) 2050 | { 2051 | return errno; 2052 | } 2053 | const char* getErrmsg(void) 2054 | { 2055 | return errmsg; 2056 | } 2057 | } 2058 | -------------------------------------------------------------------------------- /C(Linux)知识点总结.c: -------------------------------------------------------------------------------- 1 | C语言关键字: 2 | unsigned signed bool void char short int long double float struct enum union typedef sizeof 3 | if else for switch case default while do break continue goto 4 | extern register volatile auto const static return 5 | 6 | 常用的Linux系统命令: 7 | touch/cat/more/head/tall/rm/cp/mv 8 | mkdir/rmdir/cd/ls/tar 9 | ifconfig/ping/telnet/ssh/ftp 10 | vim文本编辑器: 11 | 在终端下依靠键盘操作使用的文本编辑器。 12 | 三大主要模式:正常模式、插入模式、命令模式 13 | 常用快捷键:Ctrl+x Ctrl+z Ctrl+c 14 | 教程:vimtutor 15 | 16 | C语言介绍: 17 | 发明C语言的目的是什么 18 | 长盛不衰 19 | 优缺点、特点 20 | C语言三剑客:《C语言陷阱和缺陷》、《C和指针》、《C专家编程》、《C程序设计语言》、 21 | 《C Primer Plus》、《必然》、《白说》 22 | 23 | 24 | 编译器介绍: 25 | 编译器就一个特殊的程序,把负责把C代码(文本文件)编译成可执行的二进制文件。 26 | 它由:预处理器、编译器、链接器组成 27 | 常用的参数:-E -c -S -o -std -l -D -Werror -Wall 28 | E 激活预处理;头文件、宏等展开(.i文件) 29 | S 激活预处理、编译;生成汇编代码(.s文件) 30 | c 激活预处理、编译、汇编;生成目标文件(.o文件) 31 | o 生成目标 32 | Wall 打开编译告警(所有) 33 | g 嵌入调试信息,方便gdb调试 34 | 《程序员的自我修养》 35 | 36 | 数据类型: 37 | 整型:unsigned、signed 38 | char,short,int,long,long long 39 | 实型:float,double,long double 40 | 字符:char 41 | 布尔:bool 42 | 取值范围:char,short,int 43 | 各类型的字节数: 44 | 各类型的占位符:long double(%LF) 45 | 46 | 进制转换: 47 | 为什么需要二进制:因为现在的计算机由大规模集成电路构成,计算单元只能识别高低电流这种数据, 48 | 因此只能使用二进制数据。 49 | 为什么需要八进制:为了方便记录二进制,由于历史原因八进制数据还在使用。 50 | 为什么需要十六进制:相当于升级版的八进制,由于计算机的高速发展,八进制已经无法满足需要。 51 | 十进制转换成二进制: 52 | 求余:把十进制数据不停的用2求余,逆序记录求余的结果。 53 | 189 % 2 = 1 54 | 94 % 2 = 0 55 | 47 % 2 = 1 56 | 23 % 2 = 1 57 | 11 % 2 = 1 58 | 5 % 2 = 1 59 | 2 % 2 = 0 60 | 1 % 2 = 1 61 | 10111101 62 | 求权:128 64 32 16 8 4 2 1 63 | 二进制转换成十进制: 64 | 2的不同次方相加。 65 | 二进制转换成八进制:三位二进制转换成一位八进制 66 | 10 111 101 = 0275 67 | 128 56 5 = 189 68 | 二进制转化成十六进制:四位二进制转换成一位十六进制 69 | 10 <=> a 70 | 1011 1101 = 0xbd 71 | 0b10111101 二进制 72 | 0275 八进制 73 | 0xbd 十六进制 74 | 75 | 数据在内存是如何存储的: 76 | 数据分为原码、反码、补码,内存中存储的是数据的补码。 77 | 原码:数据直接转换成的二进制(无论正负) 78 | 反码:将原码按位求反得到反码 79 | 补码: 80 | 正:原码 81 | 负:反码+1 82 | 补码:10111101 char 83 | 10111100 84 | 01000011 -67 85 | 86 | 87 | 常量与变量 88 | 常量:程序运行过程中不可改变的数据 89 | 字面值 90 | 100 int类型 91 | 100L long 92 | 100U unsigned int 93 | 100LU unsigned long 94 | 100LL long long 95 | 100LLU unsigned long long 96 | 3.14 double 97 | 3.14F float 98 | 3.14LF long double 99 | 枚举值 100 | 宏常量 101 | 具有const属性的被初始化过的全局变量 102 | 变量:类型 变量名;注意:取名规则,见名知意 103 | 容器、数据 104 | 105 | 流程控制 106 | if else,switch,for,while,do while,break,continue,goto 107 | 注意:大括号不要省略,分号不要多加 108 | 《C语言编码规范-华为》 109 | 《C++语言编程规范-谷歌》 110 | 如何判断XX类型是否是“零值”? 111 | float,bool,int,char,int* p; 112 | if(0.000001 > f && f >-0.000001) 113 | if(flag) 114 | if(0 == num) 115 | if('\0' == ch) 116 | if(NULL = p) 117 | 阅读、安全角度思考。 118 | 119 | 数组 120 | C语言中只有一维数组,多维数组都是使用一维数组模拟的。 121 | 数组的越界:一切正常、段错误、脏数据。 122 | char str[11] = "hello,world"; 123 | 变长数组:数据的长度填写变量,编译时不能确定,程序运行期间可以变化,而执行数组定义语句时长度才固定下来。 124 | 优点:可根据实际情况定义数组的长度,从而节约内存 125 | 缺点:不能初始化。 126 | int arr[10]; 127 | arr <=> int * 128 | int arr[3][4] 129 | arr <=> int (*)[4]; 130 | 131 | 函数 132 | 函数是C语言中管理代码的最小单位,命名空间独立,栈空间独立。 133 | 函数被调用时开辟栈内存,函数结束后释放栈内存。 134 | 135 | 声明:返回值 函数名(类型1,类型2,...); 136 | 定义:返回值 函数名(类型1 参数名1,类型2 参数名2,...) 137 | { 138 | 函数体 139 | } 140 | 注意:函数的定义如果出现调用之前,声明可以忽略 141 | 注意:调用函数时如果没有找到函数声明,也没有定义,编译器也不报错,而是先猜测函数的格式,链接时在尝试寻找函数的定义。 142 | return 语句只是把数据存储到一个特定的位置,当函数运行结束后,调用者就可以从这个位置获取到返回值。 143 | 函数有返回值(格式),而没写return语句,调用者会得到一个不确定的返回值。 144 | 145 | 常见编译错误: 146 | 隐式声明函数,没有找到函数声明和定义。1 147 | undefined reference to '',有函数声明,但无函数定义 148 | 函数的本质:函数就是存储在代码段中的一段数据(二进制指令的集合),函数名就是这段数据的开始位置 149 | 因此函数名就是地址,可以定义指向这段数据的指针变量, 150 | 返回值 (*函数指针) (类型1,类型2,...); 151 | 152 | 函数的传参: 153 | 1、只能值传递(内存拷贝),使用指针可提高效率(const int *) 154 | 2、函数之间共享变量,全局变量(尽量少用),指针(注意安全) 155 | 3、数组当作函数的参数时就蜕变成了指针(长度丢失),额外增加一个参数传递数组长度。 156 | 157 | 修饰变量的关键字 158 | auto:用来修饰自动创建、释放的变量(局部变量、块变量),不加就代表加。 159 | 注意:静态变量、全局变量不能用它来修饰。 160 | 161 | static: 162 | 限制作用域:全局变量、普通函数 163 | 改变存储位置:把局部变量、块变量的存储位置由栈改为bss、data 164 | 延长生命周期:把局部变量、块变量的生命周期延长与全局变量一样。 165 | static int fun(void); 166 | 167 | const:为数据提供一种"保护"机制,变量被它修饰后就不能显示修改。 168 | 也可以修饰函数的参数、返回值等。 169 | const int fun(void); 170 | 171 | volatile:告诉编译器此变量的值不稳定、易变(不优化变量的取值)。 172 | 多线程共享变量、硬件编程(裸机、驱动) 173 | 174 | register:申请把变量的存储位置改为寄存器,但申请不一定成功 175 | 注意:被它修饰过的变量不能取地址 176 | 177 | extern:声明变量,用于不同.c之间共享全局变量(只能解决编译时问题) 178 | 179 | 注意:全局变量、局部变量、块变量的变量名可以同名,由于作用域不同,会互相屏蔽。 180 | 块变量 > 局部变量 > 全局变量 (块变量屏蔽同名的局部变量) 181 | 182 | 程序在内存的分段: 183 | 代码段 test:存储的是代码所编译成的二进制指令、字符串字面值、常量 184 | 具有只读属性,一旦修改会发生段错误。 185 | 全局数据段 data:初始化过的全局变量、静态变量 186 | bss段(静态数据段):未初始化的全局变量、静态变量 187 | 程序运行前会清理为0; 188 | 栈 stack:存储局部变量、块变量、大小有限,安全。 189 | 由操作系统管理,以函数为单位使用(函数调用结束后自动释放)。 190 | 堆 heap:一般由程序员手动管理(让系统去映射),与指针配合使用,足够大,使用麻烦,释放的时间受控制 191 | 但不安全,容易产生内存碎片、内存泄漏。 192 | 193 | 指针: 194 | 什么是指针:指针是一种数据类型(无符号整数,代表内存编号),使用它定义指针变量。 195 | 0~4G(32个1)4294967295 byte 196 | 什么情况下使用指针: 197 | 1、函数之间共享变量(全局变量有命名冲突,不会被释放,浪费内存) 198 | 2、优化传递效率 199 | 因为C语言采用的是值传递(内存拷贝),会随着变量字节数的增加而降低运行效率。 200 | 而传递变量的地址,永远只拷贝4|8字节。 201 | void func(const int * p); 202 | 但使用指针变量的值可能会被修改,可以配合const进行保护。 203 | 3、配合堆内存 204 | 如何使用指针: 205 | 定义:类型 *变量名_p; 206 | 1、与普通变量一样,默认值不确定,为了安全一般初始化NULL。 207 | 2、一个*只能定义一个指针变量 208 | int *p1,*p2; 209 | 3、指针变量与普通的用法不同,为了避免混用,一般从名字上加以区别。 210 | 4、指针变量的类型决定了解决引用时访问的字节数。 211 | 赋值:变量名_p = 地址; 212 | int* p = NULL; 213 | 1、注意地址的类型 214 | 2、void*可以与任意类型的指针进行自动转换(C++中不可以) 215 | 3、要保障地址与物理内存有对应关系(映射过)。 216 | 解引用:*p; 217 | 根据指针变量中存储的内存编号,而访问内存中的数据。 218 | 这个过程可以会有段错误,但这是由于赋值了有问题的地址。 219 | 使用指针要注意的问题: 220 | 1、野指针:指向的目标不确定,解引用时不一定会出错,但未知的危险最可怕。 221 | 而且野指针一旦产生就无法分辨,而预防的方法就是不制造野指针。 222 | 1、定义指针时一定要初始化。 223 | 2、指向的目标被释放后,要及时置空。 224 | 3、不要指向随时可能被释放的目标。 225 | 2、空指针:指针变量的值等于NULL,对这个地址解引用访问时,一定会产生段错误。 226 | 因为它存储的是操作系统重启时所需要的数据。 227 | 而预防的方法就是解引用前判断(来历不明) if(NULL == p) 228 | 指针的运算: 229 | 指针+/-整数 = 指针+/-(宽度)*整数 230 | 指针-指针 = (指针-指针)/宽度 231 | 232 | 指针与数组名: 233 | 1、数组名就一个特殊的地址,它就代表数组的第一个元素的首地址,也能当指针使用。 234 | arr[i] <=> *(地址+i); 235 | 因此指针也能使用[]运算符 236 | 2、指针与目标内存是指向关系,而数组名是对应关系。 237 | 3、数组当函数的参数就蜕变为了指针变量,长度丢失,安全性不保障。 238 | void fun(int* const arr,size_t len); 239 | 指针与const的配合使用: 240 | const int* p; 241 | int const * p; 242 | int * const p; 243 | const int * p; 244 | int const * const p; 245 | 指针的高级应用: 246 | 指针数组:可以把无序的离散的数据,归纳到一起。 247 | 数组指针:专门指向数组指针 248 | 二级指针:指向指针的指针 249 | 函数指针:指向函数的指针 250 | 251 | 字符串: 252 | 由字符组成的串型数据结构,它的结束标志是'\0'。 253 | 字符串存在的形式: 254 | 字符数组:char arr[5] = {'a','b','c','d'}; 255 | 一般存储在栈,也可以存储在堆。 256 | 要考虑'\0'的位置 257 | 字符串字面值:由双引号包括的若干个字符,"hehe"。 258 | 以地址形式存在,需要使用const char* str;指针指向。 259 | 数据存在只读段,如果强行修改只会出现段错误。 260 | 背后隐藏着'\0'; 261 | char str[] = "hehe"; 262 | 一般使用字符串字面值来初始化字符数组。 263 | 字符串的输出: 264 | printf %s,puts,fprintf 265 | 字符串的输入: 266 | scanf %s:不能输入空格 267 | gets:不限制长度 268 | fgets:可能会接受到'\n',或者输入缓冲区中残留数据 269 | 字符串常见的操作: 270 | strlen/strcat/strcpy/strcmp 271 | strncat/strncpy/strncmp 272 | memset/memcpy/strstr/strchr 273 | sprintf/sscanf 用于拼接/解析字符串,非常好用 274 | 字符数据 -> 数据 计算 数据 -> 字符数据 275 | 276 | 堆内存管理 277 | C语言中没有内存管理的语句,只能借助标准库中的函数进行管理堆内存。 278 | void *malloc(size_t size); 279 | void free(void *ptr); 280 | void *calloc(size_t nmemb, size_t size); 281 | void *realloc(void *ptr, size_t size); 282 | 283 | 当向malloc首次申请内存时,malloc手中也没有内存,malloc会向系统申请,系统会映射33页内存交给malloc管理, 284 | 之后再向malloc申请内存,malloc会直接从33页内存中分配,直到33页用完,再向操作系统申请。 285 | 访问malloc分配的内存时,可以越界,但不要超过33页范围。 286 | 287 | 内存泄漏: 288 | 1、指针管理失误,指向其他位置。 289 | 2、free语句没有执行到。 290 | 3、free语句忘记写。 291 | 就内存没释放,有申请新内存,导致可用的内存越来越少,速度越来越慢。 292 | 前提:程序没有结束,当程序结束后属于它的所有资源都会被系统回收。 293 | 内存碎片: 294 | 已经释放的内存,但不能被再次使用,这叫内存叫做内存碎片。 295 | 内存碎片不是错误,它是由于内存的释放时间和分配时间不协调造成的。 296 | 内存碎片无法避免(天然形成的),只能尽量减少: 297 | 1、尽量使用栈内存,只有在数据量比较多的时候再使用堆内存。 298 | 2、尽量申请大块内存自己管理。 299 | 3、不要频繁的申请释放内存。 300 | 301 | 预处理指令: 302 | 把C代码翻译成标准的C代码叫预处理、负责翻译的程序叫预处理器、被翻译的代码叫预处理指令。 303 | 查看预处理的结果: 304 | gcc -E code.c 直接查看预处理的结果 305 | gcc -E code.c -o code.i 把预处理的结果保存到文件中 306 | 宏定义: 307 | 宏常量:用一个有意义的单词代表一个字面值数据在代码中使用,在预处理时把单词替换成数据 308 | 优点:提高可读性、安全、扩展方便 309 | 宏函数:宏函数不是真正的函数,是带参数的宏,只是使用的方法类似函数 310 | 预处理时参数会代入到表达式中,宏名会替换成后面的表达式。 311 | 优点:运行速度快(没有参数传递),类型通用,只有极精简的代码段才适合定义宏函数 312 | 缺点:不会进行类型检查,也没有返回值,只有一个计算结果,大量使用会增加代码段的冗余。 313 | 预定义的宏: 314 | __FILENAME__ 315 | __func__ 316 | __DATE__ 317 | __TIME__ 318 | __LINE__ 319 | 条件编译: 320 | #if 321 | #elif 322 | #else 323 | #endif 324 | #ifndef 325 | #ifndef 326 | 头文件卫士 327 | 328 | 复合数据类型: 329 | 结构 struct 330 | 设计数据类型 331 | typedef struct Student 332 | { 333 | char name[20]; 334 | char sex; 335 | short age:1; 336 | }Student; 337 | 338 | 定义结构变量 339 | Student stu; 340 | Student* stup = malloc(sizeof(Student)); 341 | 访问结构成员 342 | stu.name,stu.sex,stu.age 343 | stup->name,stup->sex,stup->age 344 | 计算结构的字节数: 345 | 注意:成员的顺序不同会影响结构的字节数。 346 | 对齐:假定从零地址开始,每成员的起始地址编号,必须是它本身字节数的整数倍。 347 | 补齐:结构的总字节数必须是它最大成员的整数倍。 348 | 注意:在Linux系统下计算补齐、对齐时,成员超过4字节按4字节计算 349 | 350 | 联合 union 351 | 从语法上来说与结构的用法基本类似,每个成员都从零地址开始,所有成员共有一块内存。 352 | 1、使用联合判断大小端 353 | 2、联合的总字节数计算,不需要对齐,但有补齐 354 | 枚举 enum 355 | 值受限的int类型,把变量合法的值列举出来,除此以外不能等于其他的值 356 | 枚举值是常量,可以直接使用在case后,常与switch语句配合使用 357 | 358 | 文件操作: 359 | 文件分类: 360 | 文本文件:记录的是字符串的二进制 361 | 二进制文件:直接把数据补码记录到文件中 362 | 文件打开: 363 | FILE *fopen(const char *path, const char *mode); 364 | "r" 以只读方式打开文件,如果文件不存在则打开失败,返回值为空。 365 | "r+" 在"r"的基础上增加写权限。 366 | "w" 以只写方式打开文件,如果文件不存在则创建,如果文件存在则把内容清空。 367 | "w+" 在"w"的基础上增加读取权限。 368 | "a" 以只写方式打开文件,如果文件不存在则创建,如果文件存在则把内容保留,与"w"区别是当有新数据写入,会追加到文件的末尾。 369 | "a+" 在"a"的基础上增加读权限。 370 | "b" 在linux系统下没有用,表示以二进制格式打开文件。 371 | 在Windows系统下不加b '\n' 写到文件中 系统会写入'\n\r',加b则写'\n'时只写入'\n'. 372 | 读写文本内容: 373 | int fprintf(FILE *stream, const char *format, ...); 374 | 功能:把数据以文本形式写入到文件中 375 | stream:文件指针,fopen函数的返回值 376 | format:格式化控制符,点位符等 377 | ...:要写入的变量。 378 | 返回值:成功写入的变量个数。 379 | int fscanf(FILE *stream, const char *format, ...); 380 | 功能:从文件中读取数据到变量,要求文件的内容是字符。 381 | stream:文件指针,fopen函数的返回值 382 | format:格式化控制符,点位符等 383 | ...:变量的地址 384 | 返回值:成功读取到返回0,失败返回-1。 385 | 读写二进制内容: 386 | size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 387 | 功能:内存中的数据,以二进制形式写入到文件中。 388 | ptr:要写入的内存的首地址 389 | size:要写入的字节数 390 | nmemb:要写入的次数 391 | stream:文件指针,fopen函数的返回值 392 | 返回值:成功写入的次数 393 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 394 | 功能:从文件中以二进制方式读取数据到内存中。 395 | ptr:用来存放数据的内存首地址 396 | size:要读取的字节数 397 | nmemb:要读取的次数 398 | stream:文件指针,fopen函数的返回值 399 | 返回值:成功读取的次数 400 | 文件位置指针: 401 | 每个打开的文件系统都会用一个指针记录着它的读写位置,这个指针指向哪里, 402 | 接下来对文件的读取就会从哪里继续,指针的位置会随着文件的读写自动发生变化 403 | 文件结构体中有一个成员记录文件的读写位置,称它位文件位置指针,有些情况下需要调整它的位置,获取到正确的数据。 404 | int fseek(FILE *stream, long offset, int whence); 405 | 功能:根据基础位置+偏移值调整文件指针的位置。 406 | stream:文件指针,fopen函数的返回值 407 | offset:可以为正负,正往右(偏移值) 408 | whence:(基础位置) 409 | SEEK_SET 文件头 410 | SEEK_CUR 当前位置 411 | SEEK_END 文件尾 412 | long ftell(FILE *stream); 413 | 功能:返回文件位置指针所在的位置。 414 | void rewind(FILE *stream); 415 | 功能:把文件位置指针调整到开头 416 | 文件关闭: 417 | int fclose(FILE *fp); 418 | 功能:把文件关闭,以释放相关资源,避免数据丢失。 419 | 420 | 多文件编程: 421 | 随着代码量的增加,不得不把代码分成若干个.c文件编写,这样能够给文件。。。 422 | 但缺点是不方便编译,需要借助编译脚本。 423 | 如何进行多文件编译:根据功能、责任分成若干个.c文件,然后为每个.c文件配备一个辅助文件.h然后单独编译每个.c文件, 424 | 生成目标文件.o,然后再把.o文件合并成可执行文件。 425 | 头文件中应该写什么: 426 | 1、头文件卫士 427 | 2、宏常量、宏函数 428 | 3、结构、联合、枚举的设计 429 | 4、变量、函数的声明 430 | 5、static函数的实现 431 | 432 | 编译脚本: 433 | 把用于编译的命令记录到文件中(makefile/Makefile),在终端里执行make程序时,make程序会自动读取当前目录中的。。。 434 | make程序会监控每个文件的最后修改时间,如果没有被修改的文件不需要重新编译,这样可以节约大量的时间 435 | 436 | 注意:一定要使用tab缩进 437 | 438 | GDB调试: 439 | 1、设置ubuntu系统,当段错误时产生core 440 | ulimit -c unlimited 441 | 2、编译时增加-g参数 442 | 3、再次执行新编译的程序,重新产生core文件 443 | 4、gdb a.out core 进行调试 444 | run/where -------------------------------------------------------------------------------- /Linux操作指令.c: -------------------------------------------------------------------------------- 1 | find [PATH] [option] [action] 2 | # 与时间有关的参数: 3 | -mtime n : n为数字,意思为在n天之前的“一天内”被更改过的文件; 4 | -mtime +n : 列出在n天之前(不含n天本身)被更改过的文件名; 5 | -mtime -n : 列出在n天之内(含n天本身)被更改过的文件名; 6 | -newer file : 列出比file还要新的文件名 7 | # 例如: 8 | find /root -mtime 0 # 在当前目录下查找今天之内有改动的文件 9 | 10 | # 与用户或用户组名有关的参数: 11 | -user name : 列出文件所有者为name的文件 12 | -group name : 列出文件所属用户组为name的文件 13 | -uid n : 列出文件所有者为用户ID为n的文件 14 | -gid n : 列出文件所属用户组为用户组ID为n的文件 15 | # 例如: 16 | find /home/hadoop -user hadoop # 在目录/home/hadoop中找出所有者为hadoop的文件 17 | 18 | # 与文件权限及名称有关的参数: 19 | -name filename :找出文件名为filename的文件 20 | -size [+-]SIZE :找出比SIZE还要大(+)或小(-)的文件 21 | -tpye TYPE :查找文件的类型为TYPE的文件,TYPE的值主要有:一般文件(f)、设备文件(b、c)、 22 | 目录(d)、连接文件(l)、socket(s)、FIFO管道文件(p); 23 | -perm mode :查找文件权限刚好等于mode的文件,mode用数字表示,如0755; 24 | -perm -mode :查找文件权限必须要全部包括mode权限的文件,mode用数字表示 25 | -perm +mode :查找文件权限包含任一mode的权限的文件,mode用数字表示 26 | # 例如: 27 | find / -name passwd # 查找文件名为passwd的文件 28 | find . -perm 0755 # 查找当前目录中文件权限的0755的文件 29 | find . -size +12k # 查找当前目录中大于12KB的文件,注意c表示byte 30 | 31 | -a :全部的档案,连同隐藏档( 开头为 . 的档案) 一起列出来~ 32 | -A :全部的档案,连同隐藏档,但不包括 . 与 .. 这两个目录,一起列出来~ 33 | -d :仅列出目录本身,而不是列出目录内的档案数据 34 | -f :直接列出结果,而不进行排序 (ls 预设会以档名排序!) 35 | -F :根据档案、目录等信息,给予附加数据结构,例如: 36 | *:代表可执行档; /:代表目录; =:代表 socket 档案; |:代表 FIFO 档案; 37 | -h :将档案容量以人类较易读的方式(例如 GB, KB 等等)列出来; 38 | -i :列出 inode 位置,而非列出档案属性; 39 | -l :长数据串行出,包含档案的属性等等数据; 40 | -n :列出 UID 与 GID 而非使用者与群组的名称 (UID与GID会在账号管理提到!) 41 | -r :将排序结果反向输出,例如:原本档名由小到大,反向则为由大到小; 42 | -R :连同子目录内容一起列出来; 43 | -S :以档案容量大小排序! 44 | -t :依时间排序 45 | --color=never :不要依据档案特性给予颜色显示; 46 | --color=always :显示颜色 47 | --color=auto :让系统自行依据设定来判断是否给予颜色 48 | --full-time :以完整时间模式 (包含年、月、日、时、分) 输出 49 | --time={atime,ctime} :输出 access 时间或 改变权限属性时间 (ctime) 50 | 而非内容变更时间 (modification time) 51 | 52 | ls 命令 53 | 例如: 54 | ls [-alrtAFR] [name...] 55 | -a 显示所有文件及目录 (ls内定将文件名或目录名称开头为"."的视为隐藏档,不会列出) 56 | -l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出 57 | -r 将文件以相反次序显示(原定依英文字母次序) 58 | -t 将文件依建立时间之先后次序列出 59 | -A 同 -a ,但不列出 "." (目前目录) 及 ".." (父目录) 60 | -F 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/" 61 | -R 若目录下有文件,则以下之文件亦皆依序列出 62 | 63 | 64 | cd 命令 65 | cd /root/Docements # 切换到目录/root/Docements 66 | cd ./path # 切换到当前目录下的path目录中,“.”表示当前目录 67 | cd ../path # 切换到上层目录中的path目录中,“..”表示上一层目录 68 | 69 | cp 命令,作用复制,参数如下: 70 | -a :将文件的特性一起复制 71 | -p :连同文件的属性一起复制,而非使用默认方式,与-a相似,常用于备份 72 | -i :若目标文件已经存在时,在覆盖时会先询问操作的进行 73 | -r :递归持续复制,用于目录的复制行为 74 | -u :目标文件与源文件有差异时才会复制 75 | 76 | rm命令作用为删除,参数: 77 | -f :就是force的意思,忽略不存在的文件,不会出现警告消息 78 | -i :互动模式,在删除前会询问用户是否操作 79 | -r :递归删除,最常用于目录删除,它是一个非常危险的参数 80 | 81 | mv命令作用为移动文件: 82 | -f :force强制的意思,如果目标文件已经存在,不会询问而直接覆盖 83 | -i :若目标文件已经存在,就会询问是否覆盖 84 | -u :若目标文件已经存在,且比目标文件新,才会更新 85 | 86 | pwd命令,作用为查看”当前工作目录“的完整路径 87 | pwd -P # 显示出实际路径,而非使用连接(link)路径;pwd显示的是连接路径 88 | 89 | tar命令,用于压缩解压: 90 | -c :新建打包文件 91 | -t :查看打包文件的内容含有哪些文件名 92 | -x :解打包或解压缩的功能,可以搭配-C(大写)指定解压的目录,注意-c,-t,-x不能同时出现在同一条命令中 93 | -j :通过bzip2的支持进行压缩/解压缩 94 | -z :通过gzip的支持进行压缩/解压缩 95 | -v :在压缩/解压缩过程中,将正在处理的文件名显示出来 96 | -f filename :filename为要处理的文件 97 | -C dir :指定压缩/解压缩的目录dir 98 | 99 | 压缩:tar -jcv -f filename.tar.bz2 要被处理的文件或目录名称 100 | 查询:tar -jtv -f filename.tar.bz2 101 | 解压:tar -jxv -f filename.tar.bz2 -C 欲解压缩的目录 102 | 103 | mkdir命令创建目录: 104 | mkdir [选项]... 目录... 105 | -m, --mode=模式,设定权限<模式> (类似 chmod),而不是 rwxrwxrwx 减 umask 106 | -p, --parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录; 107 | -v, --verbose 每次创建新目录都显示信息 108 | 109 | rmdir 命令删除目录: 110 | rmdir [选项]... 目录... 111 | -p 递归删除目录dirname,当子目录删除后其父目录为空时,也一同被删除。如果整个路径被删除或者由于某种原因保留部分路径,则系统在标准输出上显示相应的信息。 112 | -v --verbose 显示指令执行过程 113 | 114 | ps 命令显示运行的进程,还会显示进程的一些信息如pid, cpu和内存使用情况等: 115 | -A :所有的进程均显示出来 116 | -a :不与terminal有关的所有进程 117 | -u :有效用户的相关进程 118 | -x :一般与a参数一起使用,可列出较完整的信息 119 | -l :较长,较详细地将PID的信息列出 120 | 121 | kill 命令用于终止进程,参数: 122 | kill -signal PID 123 | 1:SIGHUP,启动被终止的进程 124 | 2:SIGINT,相当于输入ctrl+c,中断一个程序的进行 125 | 9:SIGKILL,强制中断一个进程的进行 126 | 15:SIGTERM,以正常的结束进程方式来终止进程 127 | 17:SIGSTOP,相当于输入ctrl+z,暂停一个进程的进行 128 | 129 | free 命令用于显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer: 130 | free [参数] 131 | -b  以Byte为单位显示内存使用情况。 132 | -k  以KB为单位显示内存使用情况。 133 | -m  以MB为单位显示内存使用情况。 134 | -g 以GB为单位显示内存使用情况。 135 | -o  不显示缓冲区调节列。 136 | -s<间隔秒数>  持续观察内存使用状况。 137 | -t  显示内存总和列。 138 | -V  显示版本信息。 139 | 140 | chmod命令: 141 | chmod [-R] xyz 文件或目录 142 | -R:进行递归的持续更改,即连同子目录下的所有文件都会更改 143 | #同时,chmod还可以使用u(user)、g(group)、o(other)、a(all)和+(加入)、-(删除)、=(设置)跟rwx搭配来对文件的权限进行更改,编号是各种权限的数字代码,示例: 144 | chmod 0755 file # 把file的文件权限改变为-rxwr-xr-x 145 | chmod g+w file # 向file的文件权限中加入用户组可写权限 146 | 147 | chown命令改变文件所有者: 148 | chown [para]... [owner][:[group]] file... 149 | -c 显示更改的部分的信息 150 |  -f 忽略错误信息 151 |  -h 修复符号链接 152 |  -R 处理指定目录以及其子目录下的所有文件 153 |  -v 显示详细的处理信息 154 |  -deference 作用于符号链接的指向,而不是链接文件本身 155 | 156 | sudo 用来以其他身份来执行命令,预设的身份为root: 157 | sudo(选项)(参数) 158 | 159 | -b:在后台执行指令; 160 | -h:显示帮助; 161 | -H:将HOME环境变量设为新身份的HOME环境变量; 162 | -k:结束密码的有效期限,也就是下次再执行sudo时便需要输入密码;。 163 | -l:列出目前用户可执行与无法执行的指令; 164 | -p:改变询问密码的提示符号; 165 | -s:执行指定的shell; 166 | -u<用户>:以指定的用户作为新的身份。若不加上此参数,则预设以root作为新的身份; 167 | -v:延长密码有效期限5分钟; 168 | -V :显示版本信息。 169 | 170 | vi/vim 是使用vi编辑器的命令: 171 | vi /var/log/aa.log # 打开 /var/log/aa.log文件并编辑 172 | 173 | cat 用途是连接文件或标准输入并打印。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。 174 | cat [选项] [文件]... 175 | -A, --show-all 等价于 -vET 176 | -b, --number-nonblank 对非空输出行编号 177 | -e 等价于 -vE 178 | -E, --show-ends 在每行结束处显示 $ 179 | -n, --number 对输出的所有行编号,由1开始对所有输出的行数编号 180 | -s, --squeeze-blank 有连续两行以上的空白行,就代换为一行的空白行 181 | -t 与 -vT 等价 182 | -T, --show-tabs 将跳格字符显示为 ^I 183 | -u (被忽略) 184 | -v, --show-nonprinting 使用 ^ 和 M- 引用,除了 LFD 和 TAB 之外 185 | 186 | ping 用于确定主机与外部连接状态: 187 | ping [参数] [主机名或IP地址] 188 | -d 使用Socket的SO_DEBUG功能。 189 | -f 极限检测。大量且快速地送网络封包给一台机器,看它的回应。 190 | -n 只输出数值。 191 | -q 不显示任何传送封包的信息,只显示最后的结果。 192 | -r 忽略普通的Routing Table,直接将数据包送到远端主机上。通常是查看本机的网络接口是否有问题。 193 | -R 记录路由过程。 194 | -v 详细显示指令的执行过程。 195 |

-c 数目:在发送指定数目的包后停止。 196 | -i 秒数:设定间隔几秒送一个网络封包给一台机器,预设值是一秒送一次。 197 | -I 网络界面:使用指定的网络界面送出数据包。 198 | -l 前置载入:设置在送出要求信息之前,先行发出的数据包。 199 | -p 范本样式:设置填满数据包的范本样式。 200 | -s 字节数:指定发送的数据字节数,预设值是56,加上8字节的ICMP头,一共是64ICMP数据字节。 201 | -t 存活数值:设置存活数值TTL的大小。 202 | 203 | ifconfig 命令用来查看和配置网络设备。当网络环境发生改变时可通过此命令对网络进行相应的配置: 204 | ifconfig [网络设备] [参数] 205 | up 启动指定网络设备/网卡。 206 | down 关闭指定网络设备/网卡。该参数可以有效地阻止通过指定接口的IP信息流,如果想永久地关闭一个接口,我们还需要从核心路由表中将该接口的路由信息全部删除。 207 | arp 设置指定网卡是否支持ARP协议。 208 | -promisc 设置是否支持网卡的promiscuous模式,如果选择此参数,网卡将接收网络中发给它所有的数据包 209 | -allmulti 设置是否支持多播模式,如果选择此参数,网卡将接收网络中所有的多播数据包 210 | -a 显示全部接口信息 211 | -s 显示摘要信息(类似于 netstat -i) 212 | add 给指定网卡配置IPv6地址 213 | del 删除指定网卡的IPv6地址 214 | <硬件地址> 配置网卡最大的传输单元 215 | mtu<字节数> 设置网卡的最大传输单元 (bytes) 216 | netmask<子网掩码> 设置网卡的子网掩码。掩码可以是有前缀0x的32位十六进制数,也可以是用点分开的4个十进制数。如果不打算将网络分成子网,可以不管这一选项;如果要使用子网,那么请记住,网络中每一个系统必须有相同子网掩码。 217 | tunel 建立隧道 218 | dstaddr 设定一个远端地址,建立点对点通信 219 | -broadcast<地址> 为指定网卡设置广播协议 220 | -pointtopoint<地址> 为网卡设置点对点通讯协议 221 | multicast 为网卡设置组播标志 222 | address 为网卡设置IPv4地址 223 | txqueuelen<长度> 为网卡设置传输列队的长度 224 | 225 | which 会在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果: 226 | which 可执行文件名称 227 | -n  指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名。 228 | -p  与-n参数相同,但此处的包括了文件的路径。 229 | -w  指定输出时栏位的宽度。 230 | -V  显示版本信息 231 | 232 | grep命令 233 | 该命令常用于分析一行的信息,若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工等等,比如可以加在ps, tail, cat后面 234 | 它的简单语法为 235 | grep [-acinv] [--color=auto] '查找字符串' filename 236 | 237 | clear 命令用于清除终端屏幕上现有的文字内容,将之上滚: 238 | 239 | -------------------------------------------------------------------------------- /Linux系统移植.c: -------------------------------------------------------------------------------- 1 | 一、u-boot工程 2 | 1、BootLoader介绍 3 | BootLoader是操作系统运行之前要执行的一段程序,它负责初始化硬件设备、建立内容空间映射,从而操作系统的运行做好准备,是一个专门加载操作系统的程序。 4 | 对于嵌入式系统而言,没有通用的硬件平台,因此也没有通用的BootLoader,不同的平台、不同的CPU构架都有不同的BootLoader,因为BootLoader不光依赖CPU的体系结构也依赖硬件平台的配置,对于不同的开发板而言,那怕它的CPU一样,而BootLoader都会有区别,因此我们要为每一款开发板制作它属于它的BootLoader程序。 5 | 但大部分开发板的BootLoader仍有许多共性,所以我们没有必须全部从零制作,而对一个基础的BootLoader进行修改,而制作出能用的BootLoader程序。 6 | 2、u-boot简介 7 | u-boot( Universal Boot Loader)是BootLoader的一种,它遵循GPL通用许可证的开源项目,它的源码的目录、编译形式都和Linux的源码很相似,可以说u-boot就是仿照Linux而开发的。 8 | 3、u-boot源码的获取 9 | 1、源头的代码是u-boot官网下载的,这种源码是最干净最纯粹的,一般CPU的生产厂商会下载这种源码,当CPU厂商生产出一款CPU之后就会使用这款CPU制作出一块公板(它会把这款CPU所具备的功能全部体现出来),然后会根据公板修改出一份符合它的u-boot。 10 | 2、开发板的供应商会购买CPU厂商的公板,然后对公板进行裁剪(去掉一些不需要的功能、替换掉一些不必要的硬件),然后再根据裁剪后开板有厂商提供的u-boot进行修改,从而制作出属于这款开发板的u-boot。 11 | 3、官网、CPU厂商、开发板厂商处可以获取u-boot源码,而从开发板厂商处获取的u-boot源码基本上是可以使用的,不需要做什么大的修改。 12 | 4、u-boot源码结构 13 | 顶层的u-boot源有30多个目录,大致分为三类: 14 | 1、与CPU体系结构或开发板硬件直接相关的代码。 15 | 2、能用的函数、驱动程序 16 | 3、应用程序、工具、文档 17 | board 平台相关 存储电路板相关的目录文件 18 | cpu 平台相关 与CPU的体系结构相关的库文件,由于S5PC11x的CPU早于S5PV210,而还兼容S5PV210,所以在这个目录只能看到S5PC110。 19 | include 通用 u-boot通用的一些头文件,还有一些各种硬件平台的汇编文件、系统配置和支持文件系统,configs目录有与开发板配置相关的头文件。 20 | common 通用 里面有u-boot所有支持的命令,每一个文件就代表一个命令。 21 | lib_generic 通用 里面是通用的库函数。 22 | net 通用 里面是与网络协议相关的代码。 23 | fs 通用 所支持的文件系统(管理硬盘上的文件的程序)。 24 | drivers 通用 所支持的设备驱动,如:网卡、串口、USB。 25 | disk 通用 对磁盘的支持。 26 | doc 文档 里面非常完整的u-boot说明文档。 27 | tools 通用 u-boot的一些工具 28 | examples 案例 一些能够独立运行的应用程序。 29 | 30 | 5、u-boot的配置编译 31 | u-boot是通过Makefile组织编译的,顶层的Makefile可以对开发板进行整体配置,然后递归调用各级目录下的Makefile,最后把所有编译过的代码链接成u-boot镜像。 32 | 注意:编译前确定交叉编译,Makefile的147行。 33 | 1、清理之前的编译的残留。 34 | make distclean 35 | 2、配置编译方法 36 | make x210_sd_config 37 | 注意:chmod +x mkconfig 38 | 3、多线程编译 39 | make -jn 一般n是CPU内核数量的2倍。 40 | 6、u-boot的运行 41 | 1、u-boot是ELF格式的,而u-boot.bin才是纯二进制指令格式的。 42 | 2、u-boot.bin并不能直接运行,原因是没有添加校验和,而且之前添加校和的工具不能继续使用。 43 | 3、sd_fusing目录中有为u-boot.bin添加校验和的工具,但并不能直接使用,需要在Makefile添加以下指令。 44 | 291 all: 45 | cd sd_fusing && make 46 | ./sd_fusing/mkbl1 u-boot.bin sd-bl1-8k.bin 8192 47 | dd if=sd-bl1-8k.bin of=u-boot_image.bin 48 | dd if=u-boot.bin of=u-boot_image.bin seek=48 49 | rm -rf sd-bl1-8k.bin 50 | cp u-boot_image.bin /media/sf_shared/image/ 51 | 52 | 二、u-boot常用命令 53 | 1、帮助命令 54 | help/p/? 55 | 2、环境变量相关命令 56 | printenv 显示所有环境变量 57 | setenv 添加/删除/修改环境变量 58 | saveenv 保存环境变量到硬盘 59 | 注意:环境变量都是字符串,没有数据类型之分。 60 | 注意:修改只是临时有效,重启后就会还原,必须使用保存命令才能长期有效。 61 | u-boot中的环境变量 62 | netmask:子网掩码 63 | ipaddr:板子的ip地址 64 | ethaddr:mac地址 65 | baudrate:串口波特率 66 | bootdelay:启动系统前的等待秒数 67 | gatewayip:网关ip地址 68 | bootcmd:默认启动时执行的命令 69 | bootargs:启动时传递给内核的参数 70 | serverip:服务器ip地址 71 | 72 | 3、网络相关的命令 73 | ping ip 测试与服务器是连通 74 | tftp 0x01020304 file.bin 可以从服务器下载程序到指定的内存地址。 75 | 1、ubuntu系统需要搭建tftp服务器 76 | 2、在ubuntu系统关机的情况下,设置网络模式为桥接模式,然后启动。 77 | 3、设置ubuntu的ip地址,保证ubuntu、windows、开发板三者ip地址在同一网段、子网掩码一致。 78 | 4、开发板ping通windows系统。 79 | 5、开发板ping通ubuntu系统。 80 | 6、在u-boot中执行 tftp d0020010 led.bin,可以将程序下载到开发板的d0020010地址处,go d0020010 可以到此地址执行。 81 | 注意:Windows防火墙,如果不会配置规则,建议关闭。 82 | 4、启动命令 83 | boottm 0x01020304 从该内存地址启动系统。 84 | boot 它需要与bootcmd环境变量配合,根据bootcmd的设置来启动系统。 85 | 86 | 87 | 88 | 89 | 90 | 一、u-boot编译过程分析 91 | u-boot顶层目录下的Makefile负责u-boot的整体编译过程,因此想要了解u-boot的编译过程需要阅读Makefile文件。 92 | Makefile中没有数据类型之分,全部都是字符串数据。 93 | 具体分析过程参见 Makefile_bck 文件 94 | 95 | 总结: 96 | 1、u-boot的源码不是一行行代码写出来的,而拼凑出来的,一份u-boot源码中可能包含了适用于各种开发板的源码,用条件编译进行区分的。 97 | 2、很多文件不自带的,而是配置过程根据一些原材料生成的。 98 | 3、u-boot的整体的代码构架是参考了Linux内核,而编译过程与Linux也很相似。 99 | 100 | 二、u-boot的配置过程 101 | 1、执行make x210_sd_config 命令,然后make会执行Makefile脚本中的x210_sd_config目标。 102 | 2、x210_sd_config依赖了unconfig,unconfig中会删除所有旧的头文件、mk文件。 103 | 3、然后调用了mkconfig脚本,并传递了6个参数,分别是x210_sd arm s5pc11x x210 samsung s5pc110。 104 | 4、在mkconfig脚本中先确定的开发板的名字,检查参数的数量。 105 | 5、根据参数3,删除旧的链接文件,创建新的链接文件。 106 | 6、然后把参数2、3、4、5、5写到include目录下的config.mk中 107 | 7、在include目录下创建config.h头文件,并让它实际指向,config/下的以参数1命名的头文件。 108 | 8、把0xc3e00000写入board/samsung/x210/config.mk文件中,用于指定链接地址。 109 | 110 | 三、u-boot的链接脚本 111 | u-boot的链接脚本在board/samsung/x210/u-boot.lds,与裸机课程中讲的链接脚本没有本质区别,只是复杂度高一些,文件多一些,使用的技巧多一些。 112 | 1、ENTRY(_start) 指定整个uboot的入口地址,类似于C语言中的main函数。 113 | 2、指定程序链接地址的方法有两种 114 | 在Makefile文件中通过参数 -Ttext=0xc3e00000 115 | 在链接脚本中的SECTIONS里 .=0x00000000 116 | 注意:如果两个方式都设置,优先使用-Ttext设置的地址,而u-boot实际使用的是0xc3e00000。 117 | 118 | 119 | 120 | 121 | 122 | 123 | 一、u-boot第一阶段启动流程 124 | 根据u-boot的配置过程可以找到它的链接脚本,然后根据链接脚本中代码段的排列位置找到u-boot的入口代码应该是cpu/s5pc11x/start.S文件。 125 | 1、start.S 126 | a、导入了一些头文件,这些头文件都配置过程中生成的链接文件(u-boot不能在windows目录下配置、编译)。 127 | b、config.h头文件是配置过程中生成了,里面的内容只有一行,#include ,所以实际被包含的是configs/x210_sd.h。 128 | c、x210_sd.h文件中有大量的宏,里面都u-boot所需要的一些参数。 129 | d、version.h里面只记录一行有效内容,#include "version_autogenerated.h",version_autogenerated.h是配置过程中自动生成的,里面记录是u-boot的版本号。 130 | e、asm不是u-boot中的原生目录,而配置过程中生成的软链接,实际指向是asm-arm目录,而最终被包含的应该是asm-arm/proc-armv/domain.h。 131 | f、regs.h配置过程中生成的,实际被包含的是s5pc110.h。 132 | g、16个字节的校验和,而裸机课程时mkv210_image.c的功能就添加16个字节的校验和。 133 | h、_start:函数中就是用指令模拟的异常向量表,而_start依赖了reset函数,因此真正的入口函数是reset。 134 | i、而reset函数中禁用了外部中断和快速中断,设置cpu为SVC(特权模式)。 135 | j、接下顺序执行了cpu_init_crit函数,首先禁用了二级缓存,然后设置二级缓存,最后再启动二级缓存。 136 | k、然后初始化一级缓存、禁用MMU(内存管理单元,负责内存映射),然后读取启动介绍信息,根据r2寄存器中的值,确定启动介绍,最终会以SD/MMC BOOT方式启动。 137 | l、设置sram内存中位置为栈指针,然后调用lowlevel_init函数,而此函数在board/samsung/x210/lowlevel_init.S。 138 | 2、lowlevel_init.S 139 | a、检查复位状态,原因是reset函数被执行的原因有很多,比如:冷上电、热启动、睡眠状态下的唤醒,而它们区别是冷上电需要初始化DDR内存,而其它不用,如果是冷上则继续向下执行,否则调用wake。 140 | b、关闭看门狗,初始化SRAM相关的GPIO管脚,电源锁定。 141 | c、判断当前代码在什么位置,如果在DDR内存中则直接开始加载系统,如果在SRAM中则需要初始化时钟、DDR等。 142 | d、初始化时钟,205行~385行的代码都是初始化时钟系统。 143 | e、初始化DDR内存,mem_ctrl_asm_init函数不在当前文件中,而是定义在cpu/s5pc11x/s5pc110/cpu_init.S。 144 | f、初始uart,当完成uart的初始化后,向uart发送一个'O'。 145 | g、初始化TrustZone,开户系统保护。 146 | h、关闭基带模拟,到此为止lowlevel_init的工作已经全部完成,向uart发送一个'K',然后返回start.S中。 147 | 总结lowlevel_init中一共做哪些事情: 148 | 检查复位状态、SRAM相关GPIO管理的初始化,关闭看门狗、电源锁定、初始化时钟、初始化DDR内存、初始化串口关打印'O',启动TZPC、关闭基带模拟关打开'K'。 149 | 3、Start.S 150 | a、从lowlevel_init函数中返回,再次锁定电源。 151 | b、再次设置栈指针,因为之前已经设置过了,但当时DDR内存还没有初始化只能调到到SRAM中,而此时DDR已经完成初始化可以设置到DDR内存中了。 152 | c、再交判断代码的执行位置,这次判断的目的与之前的不同,这是为了决定是否重定位而判断的,如果此时代码运行在SRAM中,则说当前代码不是完成的u-boot,而初始化工作也已经全部完成,接下来则要把完整的u-boot从SD拷贝到DDR中运行。 153 | d、确定SD卡的通道号,会根据开发板的拨码开关设置自动在内存中设置相应的值。 154 | e、确定拷贝源,也就是什么介质中拷贝u-boot,最终会调用mmcsd_boot函数。 155 | f、mmcsd_boot函数中首先调用了movi_bl2_copy函数,而movi_bl2_copy函数会调用iROM中的拷贝函数。 156 | u32(*copy_sd_mmc_to_mem)( 157 | u32 channel, 表示通道号 158 | u32 start_block, 开始扇区号 159 | u16 block_size, 要拷贝的扇区数量 160 | u32 *trg, 拷贝到的目标位置 161 | u32 init); 保留 162 | g、重定位完成后则开始执行after_copy函数,开启域访问控制(为MMU做准备)。 163 | h、接下来设置TTB(内存映射转换表),然后开户MMU。 164 | 物理地址:就是设置在生产时赋予的地址,它是根据在CPU管理上的接线位置决定的,物理地址是硬件编码一旦确定无法修改。 165 | 我们在裸机编程时使用到的寄存器的地址都是物理地址,可以通过查阅芯片手册获取并操作,物理地址的缺点就是不够灵活。 166 | 虚拟地址:就是在物理的软件操作之间加一个转换,这个转换操作就地址映射。 167 | 物理地址到虚拟地址的映射时会建立一个地址映射表,操作硬件时,只需要操作虚拟地址,然后MMU芯片会根据地址映射表自动去操作对应的物理地址,这个过程由MMU芯片负责。 168 | 地址映射的好处: 169 | 1、可以编程更灵活 170 | 2、使用代码的通用性更强 171 | 3、在映射时还可以给虚拟地址设置权限,如:可读、可写、可执行等,提高内存的安全性。 172 | 4、还可以把零散的内存映射成为一整块的内存,x210共有两个内存接入口,DRAM0可以接512M内存实际只接入了256M,DRAM1可以接1024M内存而实际只接入了256M内存,因此DRAM0与DRAM1中间是不连续的,而通过MMU的映入可以把DRAM0与DRAM1合并成一整块512M内存更方便使用。 173 | i、接下来再次设置栈指针,此次设置是在MMU开启之后,现在已经有了一块连续的512M内存,这次的设置会让内存的使用更安全,更紧凑,更节约内存。 174 | j、清理bss内存段,如果不清理全局变量和static变量就无法定义、使用。 175 | k、以后工作完成后则调用start_armboot函数,此函数位于lib_arm/board.c中,是一个C语言的函数,此时会跳转到DDR内存中开始第二阶段的u-boot运行。 176 | u-boot第一阶段启动流程总结: 177 | 1、构建异常向量表 178 | 2、设置CPU工作模式为特权模式 179 | 3、关闭看门狗 180 | 4、开发板电源锁定 181 | 5、时钟系统初始化 182 | 6、DDR内存初始化 183 | 7、初始化串口并打印OK 184 | 8、重定位 185 | 9、建立映射表并开户MMU 186 | 10、跳转到第二阶段 187 | 二、u-boot第二阶段启动流程 188 | 1、从宏观角度分析u-boot第二阶段启动流程应该做什么 189 | a、第一阶段主要是初始化CPU内部的一些硬件(看门狗、时钟、uart等),然后初始化DDR内存、重定位、开户MMU。 190 | 2、第二阶段就应该初始化CPU外部的一些硬件设备了,如:iNand、网卡、ADC。 191 | 3、u-boot本身所需要的一些事情,如:环境变量、命令。 192 | 4、完成所有的初始化工作后开始倒计时: 193 | 1、等待超时,开始加载操作、加载文件系统,然后就是操作系统启动完成。 194 | 2、按任意键,进入u-boot的命令行,在命令行中中可以执行命令、设置或查看环境变量。 195 | u-boot的命令行就是个死循环,不停的接收命令、解析命令、执行命令。 196 | 197 | 三、u-boot源码分析 198 | 1、u-boot的源码不是某个人或某人组织完成的,而是不同的组织完成了不同的工作,最终让u-boot具备引导操作系统的能力。 199 | u-boot官网负责整体代码的组织架构。 200 | 半导体生产广商,根据CPU的特性对BL1段的代码进行修改,添加。 201 | 开发板的生产广商,根据开发板的外部硬件对BL1、BL2阶段的代码进行修改、添加。 202 | 2、所有在修改u-boot代码时都遵循这样的原则: 203 | 尽量不删除代码,那代码会接下的启动流程也是选择注释掉,而不是删除。 204 | u-boot为了兼容更多的CPU会做一些对于个别CPU无意义的事情。 205 | 尽量多做不要少做,那怕做一些无意义的事情,也要让操作系统顺利启动。 206 | 3、由于u-boot只负责把操作系统启动起来,因此它不需要效率高、安全、稳定。 207 | 4、由于u-boot的代码构成比较复杂,所以质量不高,因此没有借鉴价值。 208 | 5、但u-boot代码是按照Linux内存的框架设计的,因此我们分析、研究、了解是为Linux内核的学习打开基础。 209 | 210 | 四、u-boot启动过程特征总结 211 | 1、从代码角度来看,第一阶段主要以汇编代码为主,第二阶段以C代码为主。 212 | 2、从内存角度来看,第一阶段主要运行在SRAM中,第二阶段在主要运行在DDR中。 213 | 3、从硬件角度来看,第二阶段注重的是CPU内部,第二阶段注意的是CPU外部、开发板的内部。 -------------------------------------------------------------------------------- /Linux驱动.c: -------------------------------------------------------------------------------- 1 | 一、烧写Linux系统到inand 2 | 1、烧写u-boot到inand 3 | tftp 30008000 u-boot.bin 4 | movi write u-boot 30008000 5 | 2、烧写Linux内核到inand 6 | tftp 30008000 zImage-qt 7 | movi write kernal 30008000 8 | 3、烧写文件系统到inand 9 | 开发板中已经有文件系统,无须再烧,设置启动参数即可。 10 | 4、设置启动环境变量 11 | setenv bootcmd "movi read kernel 30008000;bootm 30008000" 12 | setenv bootargs "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3" 13 | 14 | 二、挂载ubuntu系统的文件夹到开发板 15 | 目的:是为了把编译好驱动程序下载到共享目下。 16 | 1、进入开发板系统 root 123456 17 | 2、检查开板的ip地址 18 | 3、在启动脚本添加以下内容:vi /etc/init.d/rcS 19 | mount -t nfs -o nolock 192.168.1.66:/nfs /mnt 20 | 21 | 三、Linux内核下的C语言编程 22 | 1、命名习惯 23 | 普通代码: 24 | #define PI 3.14159 25 | int minVal,maxVal; 26 | void sendData(void); 27 | 内核代码: 28 | #define PI 3.14159 29 | int min_val,max_val; 30 | void send_data(void); 31 | 32 | 2、GNU C与ANSI C 33 | GNU编译器是专门为Linux内核所设计的一款编译器,而且Linux内核中也使用大量只有GNU编译才支持的C语言语法,因此只有GNU编译器才能编译内核代码和Linux驱动程序。 34 | a、零长数组 35 | typedef struct { 36 | int len; 37 | char data[0]; 38 | }Data; 39 | 40 | Data* dp = malloc(sizeof(Data)+1024); 41 | dp->len = 1024; 42 | 43 | send(sockfd,dp,sizeof(dp->len+sizeof(*dp)),0); 44 | b、case范围 45 | switch(n) 46 | { 47 | case 1 ... 5: break; 48 | case 6 ... 9: break; 49 | } 50 | c、typeof 51 | 定义一个宏求两个变量的最大值。 52 | #define MIN(a,b) ((a)>(b)?(a):(b)) 53 | #define MIN(type,a,b) ({type _a=num1++;type _b=num2++;_a>_b?_a:_b;}) 54 | 55 | MIN(int,num1++,num2++) => num1++ > num2++ ? num1++ : num2++; 56 | #define MIN(a,b) ({typeof(a) _a=a; typeof(b) _b=b; _a>_b?_a:_b;}) 57 | 58 | 定义一个宏交换两个变量值。 59 | #define swap(a,b) ({typeof(a) t=a; a=b; b =t;}) 60 | 61 | d、可变参长数宏 62 | int printf(const char *format, ...); 63 | #define debug(ftm,arg,...) printf(ftm,##arg) 64 | e、标号元素 65 | ANSI C 66 | struct Student stu = { 67 | name:"hehe", 68 | sex:'m', 69 | age:18 70 | }; 71 | 72 | GNU C 73 | struct Student stu = { 74 | .name = "hehe", 75 | .sex = 'm', 76 | .age = 18 77 | }; 78 | 79 | 3、do{ } while(0) 80 | 定义一个宏函数,用来释放堆内存。 81 | #define FREE_HEAP(p) do{free(p); p = NULL;}while(0) 82 | 83 | int func(void) 84 | { 85 | if(NULL != p) 86 | FREE_HEAP(p) 87 | else 88 | return -1; 89 | } 90 | 91 | 4、goto语句 92 | 一般的企业代码中禁止使用goto语句,因为goto可以跳转到函数内的任意位置,会打破原有分支、循环结构、或业务逻辑,所以goto是应用层代码中是一种非常危险的语句,但在内核中却有它独到的功能。 93 | int example(void) 94 | { 95 | if(!register_a()) 96 | { 97 | goto err1; 98 | } 99 | 100 | if(!register_b()) 101 | { 102 | goto err2: 103 | } 104 | 105 | if(!register_c()) 106 | { 107 | goto err3: 108 | } 109 | 110 | // work 111 | 112 | err3: 113 | unregister_c(); 114 | err2: 115 | unregister_b(); 116 | err1: 117 | unretister_a(); 118 | err: 119 | return ret; 120 | } 121 | 在内核中,资源的申请与释放必须是逆序,而goto语句能够把错误处理的代码写的安全、严谨而有简洁。 122 | 123 | 四、内核模块 124 | 通过分析Linux内核的编译过程,了解到一个实事:Linux内核是由很多模块组成了。 125 | 1、内核以模块的形式组成的好处 126 | a、可以灵活的进行裁剪 127 | b、可以动态加载或卸载 128 | c、可以尽量控制内核的大小 129 | 2、内核模块的特点 130 | a、内核模块代码最终是要加入内核中的 131 | b、内核模块代码工作在内核态。 132 | c、内核模块代码一旦出错,可能会导致整个内核(操作)崩溃。 133 | d、在Linux系统中驱动程序是一个内核模块,它按照内核模块的格式进行编写。 134 | 3、内核模块的结构 135 | a、模块加载函数(必须) 136 | 模块加载内核时自动执行此函数,相当于内核模块的入口函数。 137 | 此函数一般要做些初始化工作、申请内存、资源,注册相关信息。 138 | b、模块卸载函数(必须) 139 | 模块被卸载时自动执行。 140 | 此函数一般要做些资源的释放、销毁的工作。 141 | c、模块声明许可证(必须) 142 | d、模块作者信息声明(可选) 143 | e、其它一些自定义函数,可以被模块加载函数调用。 144 | 4、内核模块的加载与卸载 145 | insmod hello.ko 146 | rmmod hello.ko 147 | 五、printfk函数 148 | printfk是在内核中运行的向控制台输出显示信息的函数,与printf函数的用法基本一致。 149 | 内核中的消息是分等级的,定义在include/linux/kernal.h 150 | #define KERN_EMERG "<0>" /* system is unusable */ 151 | #define KERN_ALERT "<1>" /* action must be taken immediately */ 152 | #define KERN_CRIT "<2>" /* critical conditions */ 153 | #define KERN_ERR "<3>" /* error conditions */ 154 | #define KERN_WARNING "<4>" /* warning conditions */ 155 | #define KERN_NOTICE "<5>" /* normal but significant condition */ 156 | #define KERN_INFO "<6>" /* informational */ 157 | #define KERN_DEBUG "<7>" /* debug-level messages */ 158 | printk 默认的等级是 KERN_NOTICE 159 | 在操作系统中可以设置显示消息等级,数字越大,级别超低。 160 | cat /proc/sys/kernel/printk 可以看当前系统设置的消息等级 161 | echo "5" > /proc/sys/kernel/printk 162 | 163 | 164 | 165 | 一、模块参数 166 | 在加载模块时可以像应用程序一样附加一些参数。 167 | 定义变量:用来存储数据。 168 | 注册变量:让内核允许加载模块时变量被赋值。 169 | module_param(变量名,类型,权限); 170 | 类型: 171 | byte、short、ushort、int、uint、long、ulong、charp 172 | 权限: 173 | #define S_IRUSR 00400 174 | #define S_IWUSR 00200 175 | #define S_IXUSR 00100 176 | 传递参数:insmod xxx.ko 变量名=数据 177 | 模块导出符号: 178 | EXPORT_SYMBOL_GPL(符号); 179 | 导出的符号(变量名、函数名)可以被其他模块使用,使用前声明一下即可。 180 | 181 | 二、驱动概述 182 | 什么是驱动:就是控制硬件工作的软件。 183 | 在Linux系统下,驱动程序工作在内核中,以内核模块形式存在,它能能够控制硬件工作,或者提供控制硬件的接口。 184 | 驱动程序提供接口归内核调用,然后内核再统一抽象成文件(设备文件),代应用的程序以操作文件式进行控制硬件。 185 | 应用程序工作在用户态,驱动程序工作在内核态,它们之间不能直接联系,需要按照内核提供的接口(open/read/write/close)来传递数据。 186 | 在Linux系统下一切皆文件。 187 | 188 | 三、驱动的类型 189 | 字符设备:在IO过程中以字符或字节为单位进行数据传输,数据也要按照一定的顺序进行读写。 190 | 操作这种设备的接口有:open/close/read/write 191 | 常见的字符设备有:鼠标、键盘、串口、LED灯、温度传感器、湿度传感器等。 192 | 字符设备由于功能杂乱,设备也混乱,一般都是小厂家生产的没有统一的接口,所以大多数据需要编写驱动的都是字符设备。 193 | 块设备:以块为单位进行数据的读写,用记在读写数据时必须最少读一块,这种设备一般都带缓冲区,而且数据量一般都很大,数据在读写时一般不会立即到达硬件。 194 | 操作这种设备的接口有:open/close/read/write/sync/fsync/fdatasync。 195 | 常见的块设备有:硬盘、SD卡、Nand、iNand。 196 | 虽然存储介质不同,但都有一个控制器不需要外部直接操作,只需要按照控制器的接口来操作即可。 197 | 网络设备:具有网络通信协议的设备,网络设备没有设备文件,必须使用socket进行操作。 198 | 操作这种设备的接口有:send/recv/sendto/recvfrom。 199 | 网络设备都非常标准的、规范,所以有了万能的网卡驱动,顶多需要移植一下,也不需要编写驱动。 200 | 201 | 四、设备文件 202 | 在Linux系统会把硬件抽象成文件来制作(网上除外)。 203 | 查看设备文件:ls -l /dev 204 | 每个字母表示文件的类型: 205 | - 普通文件 206 | d 目录文件 207 | l 链接文件 208 | c 字符设备文件 209 | b 块设备文件 210 | p 管道文件 211 | s socket文件 212 | 设备号:主设备号+从设备号组成 213 | 主设备号:表示硬件的类型,1~254 214 | 从设备号:硬件的编号,0~255 215 | 它是在驱动程序中确定的,可以让内核自动生成,也可以手动确定,或者手动创建。 216 | 注意:设备不能重复。 217 | 218 | 五、字符设备驱动 219 | 1、字符设备驱动分析 220 | drivers\char\Cs5535_gpio.c 221 | a、按照内核模块格式编写 222 | b、创建设备号、初始化、添加 223 | c、实现文件操作函数 224 | 2、设备号 225 | dev_t dev_id = MKDEV(主设备号,从设备号); 226 | 功能:生成一个设备号 227 | 返回值:主设备号+从设备号的合体 228 | 注意:不一定能使用。 229 | 230 | int register_chrdev_region(dev_t from, unsigned count, const char *name) 231 | 功能:注册希望使用的设备号,如果已经被占用则失败。 232 | from:希望使用的设备号,MKDEV的返回值 233 | count:设备的数量 234 | name:设备文件名 235 | 返回值:成功返回0 236 | 237 | int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name) 238 | dev:内核分配的设备号,返回值 239 | baseminor:设备号的起始值 240 | count:设备的数量 241 | name:设备名 242 | 返回值:成功返回0 243 | 244 | MAJOR(dev_id) 解析出主设备号 245 | MINOR(dev_id) 解析出从设备号 246 | 247 | void unregister_chrdev_region(dev_t from, unsigned count) 248 | 功能:释放设备号 249 | from:设备号 250 | count:数量 251 | 3、字符设备结构体 252 | struct cdev { 253 | struct kobject kobj; // 内嵌的对象 254 | struct module *owner; // 模块名 255 | const struct file_operations *ops; // 文件操作结构体(函数指针) 256 | struct list_head list; 257 | dev_t dev; // 设备号 258 | unsigned int count; // 设备数量 259 | }; 260 | 261 | void cdev_init(struct cdev *cdev, const struct file_operations *fops); 262 | 功能:初始化字符设备结构体 263 | cdev:字符设备结构体 264 | fops:文件操作结构体 265 | 266 | int cdev_add(struct cdev *p, dev_t dev, unsigned count) 267 | 功能:添加字符设备到内核 268 | p:被初始化过的字符设备结构体 269 | dev:注册过的设备号 270 | count:设备数量 271 | 272 | struct file_operations { 273 | // 模块名 274 | struct module *owner; 275 | // 设备文件位置指针 276 | loff_t (*llseek) (struct file *, loff_t, int); 277 | // read函数 278 | ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 279 | // write 280 | ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 281 | // poll 282 | unsigned int (*poll) (struct file *, struct poll_table_struct *); 283 | // ioctl 284 | int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 285 | // mmap 286 | int (*mmap) (struct file *, struct vm_area_struct *); 287 | // open 288 | int (*open) (struct inode *, struct file 289 | // flush 290 | int (*flush) (struct file *, fl_owner_t id); 291 | // close 292 | int (*release) (struct inode *, struct file *); 293 | } 294 | 六、设备文件 295 | 1、手动创建 296 | mknod 创建设备文件的命令。 297 | cat /proc/devices 查看内核分配的主设备号 298 | mknod /dev/char_v1 c 250 0 299 | 300 | 2、自动创建 301 | 内核提供一组函数用于自动创建设备文件,设备类对动对应的数据结构:struct class 302 | class_create->*__class_create 303 | struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key) 304 | 功能:在内核中创建设备类 /sys/class/xxx 305 | owner:模块名 306 | key:设备类的名字 307 | 返回值:设备类指针 308 | 309 | struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...) 310 | 功能:/sys/class/xxx/在dev目录下自动创建设备文件 311 | class:设备类指针,class_create函数的返回值 312 | parent:设备结构体指针,兼容各种类型的设备,此处填写字符设备结构指针。 313 | devt:设备号 314 | drvdata:创建设备文件时的附加信息 315 | fmt:设备文件名 316 | 317 | void device_destroy(struct class *class, dev_t devt) 318 | 功能:删除设备 319 | class:设备类指针,class_create函数的返回值 320 | devt:设备号 321 | 322 | void class_destroy(struct class *cls) 323 | 功能:从内核中删除设备类 324 | 325 | 326 | 327 | 328 | 一、驱动程序中操作GPIO 329 | 在内核中已经把各个GPIO定义为宏,不需要再找地址了,在头文件 arch/arm/mach-sv5pv210/include/mach/gpio.h。 330 | int gpio_request(unsigned gpio, const char *label) 331 | 功能:申请GPIO资源 332 | gpio:GPIO管脚的编号,在gpio.h头文件中有定义 333 | label:给GPIO管脚取个名字 334 | 335 | void gpio_free(unsigned gpio) 336 | 功能:释放GPIO资源 337 | 338 | int gpio_direction_input(unsigned gpio) 339 | 功能:设置GPIO管脚为输入模式 340 | 341 | int gpio_direction_output(unsigned gpio, int value) 342 | 功能:设置GPIO管脚为输出模式 343 | 344 | int gpio_get_value(unsigned gpio) 345 | 功能:从GPIO管脚读取数据 346 | 347 | void gpio_set_value(unsigned gpio, int value) 348 | 功能:向GPIO管脚写入数据 349 | 二、ioctl 350 | int ioctl(int d, int request, ...); 351 | 功能:从应用层来调用内核层的ioctl函数,对设备进行设置。 352 | d:文件描述符,open函数的返回值 353 | request:命令 354 | ...:附加参数,存储在用户层,以指针的方式交给内核层。 355 | 返回值:成功返回0,失败返回-1。 356 | 357 | int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long arg); 358 | 功能:响应应用层的ioctl函数,对硬件进行设置。 359 | cmd:对应应用层request 360 | arg:实际上是一个指针,它第指向用户层的一段数据。 361 | 362 | 三、内核的内存管理 363 | unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n) 364 | 功能:从用户层拷贝数据到内核层 365 | to:指向内核层空间的指针 366 | from:指向用户层空间的指针 367 | n:要拷贝的字节数据 368 | 返回值:成功拷贝的字节数 369 | 370 | long copy_to_user(void __user *to,const void *from, unsigned long n) 371 | 功能:从内核层拷贝数据到用户层 372 | to:指向用户层空间的指针 373 | from:指向内核层空间的指针 374 | n:要拷贝的字节数据 375 | 返回值:成功拷贝的字节数 376 | 377 | void *kmalloc(int size) 378 | 功能:与标准C中的用法一致,其实就是调用malloc,kmalloc只是一个宏名。 379 | 380 | void kfree(void *where) 381 | 功能:释放内存 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /Makefile.c: -------------------------------------------------------------------------------- 1 | Makefile文件: 2 | 叫做编译脚本,里面记录的编译代码的命令的集合。 3 | 在终端执行make命令时,会读取名为Makefile|makefile的文件,如果大小写同时存在,会优先调用小写的makefile。 4 | 为什么需要Makefile: 5 | 1、随着项目规模的不断增加,代码量越来越多,为了更好的管理代码、协同工作、不得不把代码拆分、设计成多个文件。 6 | 2、由于源文件过多,编译时命令不易写,编译时间过长,而使用Makefile可以多目录编译,一次编写循环利用,有效节约编译时间。 7 | 3、Makefile中可以使用分支、循环、函数、系统命令,以此来控制、优化编译的过程。 8 | 9 | Makefile的一般格式: 10 | 字符串变量 11 | name = value 12 | name := value 13 | 执行目标(函数) 14 | // 默认第一个执行目标是入口,执行目标可以互相调用 15 | func:调用其他执行目标 16 | cmd ...... 17 | 注意: 18 | 1、变量与执行目标都顶格写,不要有空格 19 | 2、执行目标中的编译命令一定要使用tab键缩进。 20 | 3、#可以进行注释 21 | 22 | 为什么Makefile可以节约编译时间: 23 | make程序会检查所需要编译文件的最后访问时间,如果与上次编译时一致,就不在编译,也就是说每次执行make命令,只编译部分源文件, 24 | 重新的生成.o结尾的目标文件,然后再把所有的目标文件合并成可执行文件。 25 | -------------------------------------------------------------------------------- /Qt.cpp: -------------------------------------------------------------------------------- 1 | 一、Qt简介 2 | 1、Qt是什么 3 | Qt图形用户程序框架,是对底层应用编程接口API面向对象的封装。 4 | 是一套基于C++语言的类库,专注但不局限于图形用户界面的开发,也可以进行系统调用、网络编程、数据库、2D/3D图形处理。 5 | 特点:跨平台,支持Linux、Windows、MacOS、iOS、Android等操作系统。 6 | 2、为什么选择Qt 7 | 基于C++语言,简单、易用、面向对象(针对GTK) 8 | 优良的跨平台特性(针对MFC),工控、嵌入式。 9 | 10 | 二、Qt环境与编译工具 11 | 1、工具 12 | Qt助手:Qt参考文档,涉及了Qt中的每一个类和函数,是Qt开发的必备手册,在终端可以通过assistant命令启动。 13 | Qt构建器:他负责对源文件、头文件、界面文件进行解析生成编译脚本。 14 | qmake -project 工具当前目录里的文件,生成项目配置文件 xxx.pro 15 | qmake 根据xxx.pro文件生成Makefile脚本 16 | Qt设计师:对于带界面应用程序来说,最纯粹的方式就是通过C/C++代码来编写整个项目,但这种方式费时费力,所以我们一般不这样做界面, 17 | 而是采用一种画图的方式,自动生成对应的代码,所见即所得。 18 | Qt界面编译器:它负责把Qt设计师设计出的界面文件编译成C++代码。 19 | uic xxx.ui -o xxx.h 把界面文件编译成C++类。 20 | Qt创造器:Qt助手+Qt构建器+Qt设计师+Qt界面编译器+文本编辑器就等于Qt创建器,集成开发环境。 21 | 2、头文件和库 22 | Qt安装好之后会在安装目录下创建一个/usr/include/qt4目录,我们所使用的头文件都在这个目录下。 23 | 共享库安装在/usr/lib/i386-linux-gnu/qt4和/usr/lib/qt4目录下。 24 | 静态库需要重新下载源手动编译。 25 | 26 | 三、第一个Qt程序 27 | 1、Hello,Qt。 28 | 编译源代码 29 | 生成项目文件:qmake -project -> hello.pro 30 | 生成编译脚本:qmake -> Makefile 31 | 编译:make 32 | 运行:./hello 33 | 2、帮助手册 34 | Public Types:访问权限是 public 的成员。 35 | Properties:访问权限是 private 的,这一类的成员都有get/set函数。 36 | Public Functions:访问权限是 public 的成员函数。 37 | Public Slots:访问权限是 public 的槽函数,它可以像成员函数一样使用,但它可以与信号连接(与signal函数绑定一个信号与函数 38 | 一样),区别是Qt的信号可以自定义。 39 | Signals:信号,注意:不是函数,通过emit发送,会自动执行与它连接的槽函数。 40 | Static Public Members:静态公有成员函数,这类函数与对象无关,管理的是对象的公有资源。 41 | Protected Functions:访问权限是 protected 的成员函数 42 | Macros:与本类相关的宏定义 43 | Detailed Description:对本类的详细说明,使用方法,调用的上下文注意事项,有些类会有使用dome。 44 | 45 | 四、中文处理 46 | 注意:在Qt4.8中默认不支持 utf-8 字符编码,因此使用中文时会出现乱码。 47 | 内部编码与外部编码: 48 | Qt的应用程序的控件内部所使用的都是utf-16,这叫做应用程序的内部编码,而我们通过终端,控制输入的字符串叫外部编码。 49 | #include 50 | 51 | QTextCodec *codec = QTextCodec::codecForName("utf-8"); 52 | // 告诉Qt本地系统字符编码 53 | QTextCodec::setCodecForLocale(codec); 54 | // 告诉Qt程序中所使用的字符串的字符编码 55 | QTextCodec::setCodecForCStrings(codec); 56 | // 告诉Qt外部的字符编码 57 | QTextCodec::setCodecForTr(codec); 58 | 59 | 五、信号和槽 60 | 1、信号 61 | 当用户或系统触发一个动作,导致某个窗口发送变化,该空间就会发射一个信号,信号就可以调用一个函数,必要时还可以附加参数。 62 | 如:QPushButton 控件中就有 clicked(void) 信号。 63 | 2、槽 64 | 槽就一个普通的类成员函数,它可以是公有的、私有的、保护的,可以被重载也可以被覆盖,其参数、返回值可以是任意类型,可以被直接调用。 65 | 与普通函数的区别就是它可以被信号触发,自动执行。 66 | 如:窗口控件QWidget中默认就有 close(void) 槽函数。 67 | 3、信号和槽的连接 68 | bool connect ( 69 | const QObject * sender, // 信号发送者 70 | const char * signal, // 信号签名字符串 71 | const QObject * receiver, // 信号的接收者 72 | const char * method); // 槽函数签名字符串 73 | 74 | SIGNAL() 将信号函数签名转换成字符串 75 | SLOT() 将槽函数签名转换成字符串 76 | 77 | 六、窗口容器 78 | 从心上案例中可以看出,一个控制就一个窗口,要把所有控件集合在同一个窗口中,需要把这些控件束缚在同一个容器中,这种容器就叫 79 | 窗口容器。 80 | 1、QManWindows 81 | 主窗口容器,通常由标题栏、菜单栏、工具条、状态栏以及中央显示区组成。 82 | 2、QDialog 83 | 对话框窗口容器,管理多个不同的交互式控件 84 | 3、QWidget 85 | 纯粹的窗口容器,它是QManWindow和QDialog父类。 86 | 87 | 常用的设置有: 88 | resize(w,h) 设置窗口的大小 89 | move(x,y) 设置窗口的位置 90 | 91 | 创建控件时把容器对象的地址给控件,就可以把控件放入到容器中。 92 | 93 | 七、Qt创造器的使用 94 | 1、打开Qt创造器 95 | 在终端执行:qtcreator,右键锁定到启动器。 96 | 2、创建项目 97 | 文件 -> 新建文件或工程 -> Qt控件项目 -> QtGUI应用 -> 填写项目名称(不要使用中文) -> 桌面 -> 选择窗口容器 -> 设置类名、 98 | 头文件名、源文件名 -> 选择是否创建界面(设置界面文件名) 99 | 3、窗口类的构成: 100 | mainwindow.ui 文件会自动生成 ui_mainwindow.h(在Qt创造器中不可见)。 101 | ui_mainwindow.h 头文件中会自动生成 Ui_MainWindow类,该类中会包含所有mainwindow.ui文件中所画的控件。 102 | 在ui_mainwindow.h头文件中会在Ui名字空间中自动创建MainWindow类,Ui_MainWindow会被MainWindow类继承。 103 | mainwindow.h头文件中会自动创建MainWindow类,默认继承QMainWindow,会有一个默认的成员Ui::MainWindow *ui,它里面有所有 104 | 界面文件中的控件。 105 | 106 | 八、Qt常用控件 107 | QLabel:标签控件,同来显示简单的文本,在界面上只读的 108 | setText 设置显示内容 109 | text 获取标签上的字符串,返回值是QString 110 | QPushButton:按钮控件 111 | 同样具有setText、text 112 | 重要的功能是 单击信号 clicked(),也有双击、悬停、按下、弹起信号。 113 | QRadioButton:单选按键,同窗口下只能有一个被选中 114 | 重要功能是选中和取消选中信号 115 | QLineEdit:单行输入文本框 116 | 常用操作时获取它的输入内容 117 | QTextEdit:多行输入文本框 118 | 常用于输入大段文字,输入的内容可以换行,自带滚动条。 119 | QComboBox:下拉输入框 120 | 它输入的内容只能通过下拉按键选择,是防止用户输入错误常用解决方法。 121 | QTextBrower:多行文本显示,支持富文本显示。 122 | 在界面上只读,文字可以设置字号、字体、颜色、加粗、对齐等操作。 123 | QGroupBox:分组框,可以放入一个控制统一管理。 124 | 最常用的是放入:QRadioButton按钮,一组的按钮只能有一个被选中。 125 | QxxxSilder:滑块 126 | 有水平、垂直滑块,也可以当进度条使用。 127 | QSpinBox:数字滑块 128 | 与Silder的用法基本一致,只不过它是以数字显示。 129 | Silder和SpinBox主要操作时:初值、最大值、进步值。 130 | 即可以用来显示,也可以用来设置。 131 | 132 | 九、Qt创造器常用快捷键及操作 133 | 快捷键: 134 | Alt+0:显示/隐藏侧边栏 135 | Ctrl+e 松开后按0:删除所有的分割窗口 136 | Ctrl+e 松开后按1:删除多余的分割窗口 137 | Ctrl+e 松开后按2:水平分割窗口 138 | Ctrl+e 松开后按3:垂直分割窗口 139 | Ctrl+B+S:保存所有文件并编译执行 140 | Ctrl+B:编译执行 141 | Ctrl+R:运行项目 142 | Ctrl+/:注释/取消注释 143 | Ctrl+I:自动缩进选中的代码 144 | Ctrl+Shift+R:批量修改变量名 145 | Ctrl+F:搜索、替换 146 | Ctrl+单击:跳转到定义位置 147 | Shift+F2:声明与定义之间进行切换 148 | Alt+Shift+u:小写转换成大写 149 | Alt+u:大写转换成小写 150 | F1:光标定位在类或对象上,打开其帮助手册 151 | F4:头文件与源文件之间快速切换 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 一、 自定义信号和槽 186 | 1、信号和槽必须定义在类中 187 | 2、自定义信号和槽类必须继承QObject 188 | 3、在类的开头写上Q_OBJECT 没有分号,此标识符必须写在项目构建之前,如果是后加上的,必须重新构建项目。 189 | 4、信号定义在 signals:关键字下面。 190 | 5、通过emit发送信号 191 | 192 | 6、一个信号可以连接多个槽,当该信号发射会触发多个槽函数。 193 | 7、多个信号可同时连接一个槽,只要有一个信号发射就会触发槽函数。 194 | 195 | 二、界面布局 196 | 1、在设计界面时,一般不建议直接设计窗口或控件的大小(像素点数),除非容器固定大小,否则界面改变大小时布局会乱(丑)。 197 | 2、如果不固定窗口的大小一般使用自动布局,前提是在窗口添加一个布局器,然后设置窗口为自动布局(在窗口的空白位置右击 -> 布局 198 | -> 水平、垂直、栅格)。 199 | 3、在窗口内部想让某些控件自动排列及设置大小,可以选择使用布局器: 200 | 水平布局:QHBoxLayout 201 | 垂直布局:QVBOXLayout 202 | 栅格布局:QGridLayout 203 | 布局器中的控件会根据布局器的大小自动排列控件,自动设置控件的大小。 204 | 4、设置窗口自动布局,控件会随窗口的大小而变化,可以使用“弹簧”来约束控件的位置。 205 | 206 | 三、项目资源 207 | 1、项目的资源一般是指项目中使用到的图片、音频、视频等。 208 | 2、使用图片文件有两种方式: 209 | 1.使用QPainter和QImage类读取资源文件,然后绘制在窗口或控件上。 210 | 2.创建Qt资源文件,统一管理图片等资源文件。 211 | 3、在窗口或控件上绘制图片 212 | 1.由于QPainter只能工作在paintEvent函数中,因此必须覆盖此函数。 213 | 2.QImage控制图片文件的路径即可,创建QImage对象。 214 | 3.QPainter创建时需要提供在窗口的地址。 215 | 4.QPainter对象调用drawImage成员函数绘制图片。 216 | 5.paintEvent会被update自动调用。 217 | 注意:不建议使用此方法显示图片,更多的是进行绘制图形。 218 | 4、Qt资源文件 219 | 1.选中项目右击 -> 添加新文件 -> Qt -> Qt资源文件 -> 填写资源文件名 220 | 2.双击打开创建好的资源文件 -> 添加 -> 添加前缀 -> 添加文件 -> 选择要添加的文件 221 | 3.可以在设计师中使用资源文件,选择控件右击 -> 改变样式表 -> 添加资源 -> image等。 222 | 4.也可以在代码中调用setStyleSheet函数,为控件设置样式。 223 | 224 | 5、设置控件样式表的技巧 225 | 1、在为窗口设置样式表后,窗口中的控件也会继承这个样式,我的解决方法是为控件也设置样式表,覆盖窗口的样式表。 226 | QWidget 227 | { 228 | background-color: rgb(255, 255, 255); 229 | } 230 | 231 | QPushButton 232 | { 233 | background-color: rgb(255, 245, 66); 234 | // 设置按钮的边框 235 | border: 1px solid rgb(124, 124, 124); 236 | // 设置按钮的圆角弧度 237 | border-radius: 5px; // 设置按钮的圆角 238 | } 239 | 2、设置控件的悬停样式 240 | QPushButton:hover 241 | { 242 | } 243 | 3、设置按钮按下时的效果 244 | QPushButton:pressed 245 | { 246 | } 247 | 248 | 四、定时器 249 | Qt中的QTimer有两种使用方式: 250 | 信号: 251 | start成员函数,开启定时器并设置定时器的时间间隔。 252 | 事件到后会发出timeout信号,连接槽函数即可。 253 | 事件: 254 | 继承QTimer 类,对该类进行扩展,并覆盖 void timerEvent(QTimerEvent *e) 事件。 255 | void start(int msec)成员函数,开启定时器并设置定时器的时间间隔。 256 | 时间到后会自动执行timerEvent函数。 257 | 注意:时间函数被执行就会再产生timeout信号。 258 | 259 | 五、线程 260 | Qt中的线程类不能直接使用,必须继承QThread并实现run函数。 261 | 新的线程类对象调用start成员函数时会自动执行run函数。 262 | 1、线程的终止 263 | 线程对象调用 void terminate() 成员函数会终止线程,但并不会立即终止,这取决于线程的调度策略。 264 | terminate类似于线程的取消操作,而线程也可以回收 265 | 线程真正终止时会发射 void terminated() 信号。 266 | 也可以使用wait函数等待线程的终止,还可以回收线程的资源。 267 | 类似POSIX线程里的pthread_join函数。 268 | 2、线程ID 269 | static Qt::HANDLE currentThreadId() 270 | 获取当前线程的id 271 | static QThread* currentThread() 272 | 获取当前线程的句柄 273 | 274 | 六、鼠标与键盘事件 275 | 空间其实一直都在监控鼠标与键盘,当鼠标与键盘发生操作时会调用控件中的事件函数,事件函数默认什么都不做,如果想让事件函数做一些 276 | 操作,需要继承控件并覆盖事件函数。 277 | // 鼠标按下 278 | void mousePressEvent(QMouseEvent *); 279 | // 鼠标弹起 280 | void mouseReleaseEvent(QMouseEvent *); 281 | // 鼠标双击 282 | void mouseDoubleClickEvent(QMouseEvent *); 283 | // 鼠标按下并移动 284 | void mouseMoveEvent(QMouseEvent *); 285 | // 键盘按下事件 286 | void keyPressEvent(QKeyEvent* e); 287 | 成员函数 text 可以获取到按键的字符 288 | 成员函数 key 可以获取到键值 与 Qt::Keyx 进行比较 289 | // 键盘弹起事件 290 | void keyReleaseEvent(QKeyEvent* e); 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 一、线程同步 320 | 互斥量(互斥锁) 321 | QMutex 就是POSIX中的pthread_mutex_的封装。 322 | 有两个成员函数lock、unlock。 323 | 324 | 读写锁 325 | QReadWriteLock它比QMutex更实用 326 | 常用的三个成员函数lockForRead、lockForWrite、unlock 327 | A读 B读 OK 328 | A写 B读 No 329 | A写 B写 No 330 | 331 | 信号量 332 | QSemaphore用于管理多个资源 333 | QSemaphore构造时设置资源数量 334 | acquire(n) 获取资源 减去资源数 如果不够减则等待 335 | release(n) 归还资源 加上资源数 336 | 337 | 二、网络通信 338 | 使用网络模块前要先在.pro文件中添加network 339 | QT += core gui network 340 | 341 | QUdpSocket类是Qt对UDP协议加socket的封装。 342 | 1、创建QudpSocket类对象 343 | 2、绑定ip地址和端口号 344 | 3、链接readyRead()信号,当此信号发射后,就表示可以接收数据了。 345 | 4、在槽函数中调用readDatagram函数接收数据 346 | qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = 0, quint16 *port = 0); 347 | host:发送者的地址 348 | port:返回时的端口号 349 | 5、返回数据 350 | qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port); 351 | host和port是readDatagram函数收到的。 352 | 353 | 注意:每个QUdpSocket对象都需要绑定一个地址和端口号。 354 | 355 | 练习1:使用QUdpSocket实现一个带界面的点对点程序(地址、端口、接收、发送、按钮) 356 | 357 | Qt中封装了TCP协议QTcpServer、QTcpClient 358 | QTcpServer负责服务端 359 | 1、创建QTcpServer对象 360 | 2、监听list需要的参数时地址和端口 361 | 3、当有新的客户端连接成功时,会发射newConnection信号。 362 | 4、在newConnection信号的槽函数中,调用nextPendingConnection函数,获取新连接QTcpSocket对象。 363 | 5、连接QTcpSocket对象的readyRead信号 364 | 6、在readyRead信号的槽函数使用read接收数据 365 | 7、调用write成员函数发送数据 366 | 367 | QTcpSocket负责客户端 368 | 1、创建QTcpSocket对象 369 | 2、当对象与Server连接成功时,会发射connected信号 370 | 3、调用成员函数connectToHost连接服务器,需要的参数地址和端口号 371 | 4、connected信号的槽函数中开启发送数据 372 | 5、使用write发送数据,read接收数据 373 | 374 | 练习:实现Qt版的聊天室 375 | 376 | 377 | 378 | 379 | 380 | 381 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ikaros-521/Learning-Notes/f6b00721f5e9adebefd0e9af674ac13c134eabd7/README.md -------------------------------------------------------------------------------- /UML.cpp: -------------------------------------------------------------------------------- 1 | 一、UML概述 2 | 1、什么是UML 3 | 统一建模语言(Unified Modeling Language)。用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的开发方法。 4 | UML展现了一系列最佳工程实践,这些最佳实践在对大规模,复杂系统进行建模方面,特别是在软件架构层次已经被验证有效。 5 | 项目开发的说明书(设计图)。 6 | 2、UML是做什么的 7 | 帮助建立软件开发过程和各种模型 8 | 主要目的是为了让开发者之间进行有效的交流。 9 | 开发者与设计者之间的交流 10 | 开发者与开发者之间的交流 11 | 开发者与管理者之间的交流 12 | 适合大型项目和大型团队的开发 13 | 14 | 3、UML的应用领域 15 | UML的目标是以面向对象的方式来描述任何类型的系统,常用于建立软件的模型。 16 | 4、我对UML的看法 17 | 不建议用UML生成代码。 18 | 没有完美的UML图,够用就行,图是死的,人是活的。 19 | UML主要功能是为了更好的让开发团队与用户之间进行沟通交流。 20 | 先结对学习UML目的以应用、看懂为主(有利于后期工作),以画图为辅。 21 | 但需要长期学习,为后期成为系统分析师、架构师、产品经理、项目经理打下基础。 22 | 23 | 二、Rational Rose四种视图与UML的8中图形关系 24 | Rose启动后自动创建四种视图: 25 | 用例视图: Use Case View 26 | 帮助理解和使用系统,这种图展示了系统的参与者和用例是怎么相互作用的,包括了系统中的所有角色。 27 | 用例图:Use Case Digram 28 | 时序图:Sequence Digram 29 | 协作图:Collaboration Digram 30 | 活动图:Activity Digram 31 | 这种视图的主要使用都是用户、分析人员、项目管理人员。 32 | 逻辑视图: Logical View 33 | 逻辑视图关注的是系统如何实现用例中提出的功能,它提供系统的详细图形,描述了组件间如何关联。 34 | 类图:Call Digram 35 | 状态图:Statechart Dirgam 36 | 这种视图的主要使用者是开发人员,开发人员包括:程序员、需求分析师、测试工程师、项目经理。 37 | 构件视图: Component View 38 | 这种视图关注的是代码模板间的关系,通过组件视图可以知道哪些代码是组件,哪些是代码库,哪些是运行组件,会默认有一个 39 | Component图表。 40 | 这种视图的主要使用者是负责控制代码、编译、部署应用程序的人员。 41 | 部署视图: Deployment View 42 | 这种视图关注的是系统的实际部署,进程和设备间的实际连接。 43 | 这种视图的使用者是用户和软件的发布人员。 44 | 45 | 三、用例图:详细表示所有的用例需求,不考虑细节 46 | 1、用例图的作用 47 | 主要用它来描述需求,画图时要从用户的角度出发,主要用于与用户之间的交流。 48 | 2、用例图的要点 49 | 正确反应用户的需求 50 | 每个用例的内部实现 51 | 细节不是本阶段要考虑的问题 52 | 3、用例图包括 53 | 参与者 54 | 用例 55 | 用例之间的关系 56 | 4、如何确定参与者 57 | 谁使用系统 58 | 谁负责启动、关闭系统 59 | 谁查询数据 60 | 谁提供数据 61 | 谁安装、升级系统 62 | 5、什么是用例 63 | 是系统的使用过程,一个用例就是一个功能需求。 64 | 65 | 四、类图:主要表示类与类之间的关系,不用详细考虑类的属性和方法 66 | 1、类图的主要作用 67 | 主要用来描述类的内部结构和类与类之间的关系。 68 | 类图中不显示暂时性的消息,是面向对象剑魔时的主要组成部分。 69 | 2、类图主要包括 70 | 名称:类名 71 | 属性:类的成员变量 72 | 访问权限 73 | 变量名 74 | 类型 75 | 操作:类的成员函数 76 | 访问权限 77 | 函数名 78 | 参数名 79 | 参数类型 80 | 返回值类型 81 | 职责:说明此类具体负责的任务,解决什么问题 82 | 3、类之间的关系 83 | 继承关系:空心箭头实线来表示继承关系,子类指向父类 84 | 一个类Test继承Base类的属性和行为,并可以增加自己的属性和行为。 85 | 实现关系:空心箭头虚线来表示继承关系,子类指向父类 86 | 一个类Test继承一个抽象类,父类中的函数定义为纯虚函数,子类继承后覆盖所有纯虚函数。 87 | 依赖关系:箭头和虚线来表示依赖关系,依赖者指向被依赖者 88 | 一个类A中使用到类B(类A中的函数的参数或返回值类型是类B),这种关系是一种偶然、临时的,是一种非常弱的关系。 89 | 关联关系:箭头和实线来表示关联关系,依赖者指向被依赖者 90 | 是一种强依赖关系,类A的属性(成员变量)是类B。 91 | 聚合关系:实心菱形和箭头实线来表示聚合关系,菱形指向整体,箭头指向部分。 92 | 聚合关系是一种关联关系的特例,它体现的是整体与部分。 93 | 整体与部分之间是可分离,它们可以具有各自的生命周期。 94 | 从代码层面来讲与关联是一致的。 95 | 组合关系:实心菱形和箭头实线来表示组合关系,菱形指向整体,箭头指向部分。 96 | 组合关系也是一种聚合关系的特例,这种关系比聚合更强,也称为强聚合,同样也体现出整体与部分,但此时的整体与部分是 97 | 不可分隔的。 98 | 从代码层面来讲与关联是一致的。 99 | 从代码实现层面只有继承、包含、依赖。 100 | 用例图和类表示对象之间消息的发送顺序,值考虑正确情况。 101 | 102 | 五、时序图:主要表示对象之间消息的发送顺序,只考虑正确情况。 103 | 用来表示对象之间的关系,同时强调对象之间的交互。 104 | 时序图向用户表示时间随着时间的推移,清晰的可视化轨迹。 105 | 时序图用来表示项目中的某个操作中如何进行的,具体的步骤是什么。 106 | 107 | 六、状态图 108 | 用来表示对象的状态变化,用于帮助开发人员理解系统中对象的行为。 109 | 主要有状态,生命周期,条件,状态转换,事件和动作。 110 | 111 | 七、活动图 112 | 活动图的本质就是流程图,它描述了系统的活动、判断点和分支。 113 | 侧重于操作而不是对象,重点表示逻辑变化。 114 | 一个动作的流程图一般包括起点、终点、操作。 115 | 泳道:用于多种角色参与一个过程,一个活动只能属于一个泳道。 116 | 117 | 八、协作图 118 | 主要用来描述对象之间的交互关系,强调参与交互的各对象的组织。 119 | 需要按照组织对控制流程进行建模时选择画协作图。 120 | 协作图与时序图的区别: 121 | 时序图描述了交互过程中的时间顺序,但没有表达对象之间的关系。 122 | 协作图描述了对象间的关系但时间顺序必须从时序图中获得。 123 | 合格的协作图和时序图主义相同,可互换,而不丢失信息。 -------------------------------------------------------------------------------- /const_int_指针.c: -------------------------------------------------------------------------------- 1 | 一:const int a;   int const a ; 2 | 3 | 这两个的作用是一样的,a都被定义成一个常整型数,一旦被定义后,就不能再其他地方重新赋值。 4 | 5 | 二:const int * a; 6 | 7 | 1:const修饰的是整型数 int,而不是指针,即a是一个指向常整型数的指针。近一步的理解为:整型数 *a是不可以被重新赋值的,而指针却是可以修改的,可以重新指向另外一个内存地址; 8 | 9 | 2:eg: 10 | 11 | int a1=30; int a2=40; 12 | const int *p=&a1; //此时输出*p,结果为30 13 | p=&a2; //此时输出*p,结果为40 14 | a2=80; // 不能用*p=80来直接赋值,因为*p被const修饰,不能重新赋值 15 | printf("%d\n",*p); //此时输出*p,结果为80 16 | 三:int * const a; 17 | 18 | 1:const修饰的是指针a,而不是整型数*a,*a为变量而不是一个常量。即a是指向一个可以修改的整型数的常指针。进一步的理解为:指针指向的整型数是可以被修改的,但是指针不可以被修改。 19 | 20 | 2:eg: 21 | 22 | int a1=30,int a2=40; 23 | int * const p=&a1; //此时输出*p的结果是 30 24 | a1=80; // 可以用*p=80来代替,即可以用*pl来修改a1的值 25 |  //p=&a2;   这句话是错误的,因为指针p被定义为一个常指针,不能再指向一个新的内存地址 26 | 27 | 28 | printf("%d\n",*p); //输出的结果是80 29 | 四:int const * a const; 30 | 31 | a是指向一个常整型数的常指针,即指针不可修改,指针指向的整型数也是不可以被修改的。 32 | 33 | 五:总结 34 | 35 | 1:const离谁近,谁就不可以被修改 36 | 37 | 2:const在谁后面谁就不可以被修改,const在最前面的话则将其后移一位,其作用是等效的 38 | 39 | 3:即使不适用关键字const,也很容易写出功能正确的代码,那为什么还要如此看重关键字const呢?其原因在于关键字有其特殊的作用:它有可能产生更加紧凑的代码;它定义了一个常数,必然有其意义,可以向用户传递其特殊含义;合理使用关键字const,可以使编译器保护那些不希望被修改的参数,防止无意代码的修改,减少bug的产生。 40 | -------------------------------------------------------------------------------- /gdb调试.c: -------------------------------------------------------------------------------- 1 | 一、gdb借助core文件快速定义错误 2 | 1、Ubuntu系统默认情况不会产生core文件 3 | ulimit -c unlimited 设置让操作系统在进程出错的情况下产生core文件 4 | 也可以把此命令记录到用户主目录下的 .bashrc文件中 5 | 2、编译代码时增加 -g 参数 (再次) 6 | 编译时添加调试信息 7 | 3、运行带调试信息的程序 8 | 此时产生的core文件才是能跟gdb配合 9 | 4、使用gdb+core运行程序 10 | gdb ./a.out core 11 | 5、run/where 命令可以快速定位错误 12 | 13 | 二、参考printf的调试方法来理解gdb 14 | 1、编译代码时增加 -g 参数 15 | 2、使用 l命令查看代码 16 | l 默认显示下面10行代码 17 | l函数名 查看某个函数的代码。 18 | l行号 19 | 3、思考在哪里打断点,断点就是让程序在某一行停下来不在运行。 20 | 4、模拟printf的操作,查看变量的信息 21 | print 变量名 22 | 5、continue 跳过断点,继续执行 23 | 6、单步 24 | s 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数; 25 | n 执行一行源程序代码,此行代码中的函数调用也一并执行。 26 | 27 | 三、使用gdb调试死锁 28 | 1、ps -aux | grep "程序名" 查询进程ID 29 | 2、sudo gdb -p 进程ID 30 | 3、thread app all bt 31 | 4、按return键显示所有线程的栈信息 -------------------------------------------------------------------------------- /io流/微信图片_20190821192215.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ikaros-521/Learning-Notes/f6b00721f5e9adebefd0e9af674ac13c134eabd7/io流/微信图片_20190821192215.png -------------------------------------------------------------------------------- /io流/微信图片_20190821192254.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ikaros-521/Learning-Notes/f6b00721f5e9adebefd0e9af674ac13c134eabd7/io流/微信图片_20190821192254.png -------------------------------------------------------------------------------- /io流/微信图片_20190821192258.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ikaros-521/Learning-Notes/f6b00721f5e9adebefd0e9af674ac13c134eabd7/io流/微信图片_20190821192258.png -------------------------------------------------------------------------------- /sort.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #define swap(a,b) {typeof(a) t=a;a=b;b=t;} 7 | 8 | // 冒泡排序 9 | void bubble_sort(int* arr,int len) 10 | { 11 | for(int i=len-1; i>0; i--) 12 | { 13 | bool flag = true; 14 | for(int j=0; j arr[j+1]) 17 | { 18 | swap(arr[j],arr[j+1]); 19 | flag = false; 20 | } 21 | } 22 | if(flag) break; 23 | } 24 | } 25 | 26 | // 插入排序 27 | void insert_sort(int* arr,int len) 28 | { 29 | for(int i=1; i=0; j--) 34 | { 35 | if(t < arr[j]) 36 | { 37 | arr[j+1] = arr[j]; 38 | index = j; 39 | } 40 | } 41 | if(index != -1) 42 | arr[index] = t; 43 | } 44 | } 45 | 46 | // 选择排序 47 | void select_sort(int* arr,int len) 48 | { 49 | for(int i=len-1; i>0; i--) 50 | { 51 | int max = i;// 存储最大数的坐标 52 | for(int j=0; j arr[max]) 55 | max = j; 56 | } 57 | swap(arr[i],arr[max]); 58 | } 59 | } 60 | 61 | void _quick_sort(int* arr,size_t left,size_t right) 62 | { 63 | if(left >= right) return; 64 | int pi = (left+right)/2; //计算标杆的下标 65 | int pv = arr[pi]; // 备份标杆的值 66 | int l = left, r = right; // 备份左右下标 67 | while(l < r) // 左右下标相遇时结束 68 | { 69 | while(l < pi && arr[l] <= pv) l++; // 在标杆的左边寻找比它大的数据 70 | if(l < pi) // 如果没有超出范围,说明找到比标杆大的值 71 | { 72 | arr[pi] = arr[l]; // 与标杆交换位置,并记录新的标杆下标 73 | pi = l; 74 | } 75 | while(pi < r && arr[r] >= pv) r--; // 76 | if(pi < r) // 如果没有超出范围,说明找到比标杆小的值 77 | { 78 | arr[pi] = arr[r]; 79 | pi = r; 80 | } 81 | arr[pi] = pv; // 还原标杆的值 82 | if(pi-left > 1) _quick_sort(arr,left,pi-1); 83 | if(right-pi > 1) _quick_sort(arr,pi+1,right); 84 | 85 | } 86 | } 87 | 88 | // 快速排序 89 | void quick_sort(int* arr,size_t len) 90 | { 91 | _quick_sort(arr,0,len-1); 92 | } 93 | 94 | void create_heap(int* arr,size_t root,size_t len) 95 | { 96 | if(root >= len) return; 97 | int left = root*2+1; 98 | int right = root*2+2; 99 | 100 | create_heap(arr,left,len); 101 | create_heap(arr,right,len); 102 | 103 | int max = root; 104 | if(left < len) 105 | { 106 | if(arr[left] > arr[max]) 107 | max = left; 108 | } 109 | if(right < len) 110 | { 111 | if(arr[right] > arr[max]) 112 | max = right; 113 | } 114 | if(max != root) 115 | swap(arr[max],arr[root]); 116 | } 117 | 118 | // 堆排序 119 | void heap_sort(int* arr,size_t len) 120 | { 121 | for(int i=0; i= right) return; 152 | int pi = (left+right)/2; 153 | _merge_sort(arr,left,pi,temp); 154 | _merge_sort(arr,pi+1,right,temp); 155 | merge(arr,left,pi,right,temp); 156 | } 157 | 158 | void merge_sort(int* arr,size_t len) 159 | { // 把元素差分成1个个,然后标杆左右两侧进行2块2块的比较 160 | int temp[len]; 161 | _merge_sort(arr,0,len-1,temp); 162 | } 163 | 164 | void show_arr(int* arr,size_t len) 165 | { 166 | for(int i=0; i,v1叫做弧头,v叫做弧尾。 12 | 注意:若不存在顶点到自身和边,也不存在重复出现的边这种图叫做简单图,数据结构课程中讨论的都是简单图,数据结构课程中讨论的都是简单图。 13 | 14 | 在有向图中如果任意两个顶点之间存在方向相反的两条弧,这种图叫做有向完全图。 15 | 16 | 图中有很少边或弧的图叫做稀疏图,反之叫做稠密图。 17 | 18 | 如果图中的边或弧有相关的数据,数据称为权,这种图也叫做网(带权图)。 19 | 如果G(v,E)和G1(v1,E1),存在v1∈V,且E1∈E,那么G1是G的子图。 20 | 21 | 顶点与边的关系 22 | 顶点的度:指的是顶点相关联的边或弧的条目数。 23 | 有向图又分为出度和入度。 24 | 入度:其他顶点到该顶点的弧的条目数。 25 | 出度:从该点出发到其他顶点的弧的条目数。 26 | 顶点序列:从一个顶点到另一个顶点的路径,路径长度指的是路径上的边或弧的条目数。 27 | 28 | 连通图相关术语: 29 | 在无向图中,在顶点v到v1之间有路径,则称v到v1之间是连通的,如果任意两个顶点都是连通的,那么这种图称为连通图。 30 | 无向图中的极大连通子图称为连通分量: 31 | 1、必须是子图 32 | 2、子图必须是连通的 33 | 3、连通子图含有极大的顶点数 34 | 在有向图中,任意顶点之间都存在路径,这种图叫做强连通图。 35 | 有向图中的极大连通子图称为有向的强连通分量。 36 | 在有向图中如果有一个顶点的入度为0,其他顶点的入度均为1,则是一颗有向树。 37 | 38 | 图的存储结构: 39 | 图的存储主要是两个方面:顶点,边 40 | 邻接矩阵: 41 | 一个一维数组(顶点)和一个二维数组(边,弧)组成。 42 | 二维数组i,i位置都是0,如果是无向图则数组对称(左上到右下对称)。 43 | 优点: 44 | 1、非常容易判定两顶点之间是否有边 45 | 2、非常容易计算任意顶点的入度和出度。 46 | 3、非常容易统计邻接点 47 | 缺点:如果存储稀疏图,会非常浪费存储空间 48 | 邻接表: 49 | 由顶点表 边表组成。 50 | 顶点 下一个邻接点地址 顶点下标|下一个邻接点地址 51 | A -> [1] -> [3] -> NULL 52 | B -> [2] -> NULL 53 | C -> [4] -> [3] -> NULL 54 | D -> 55 | E -> 56 | 优点: 57 | 1、节省存储空间 58 | 2、非常容易计算出度 59 | 缺点:不方便计算入度 60 | 十字链表:由于邻接表不能同时兼顾出度和入度,因此我们修改邻接的边表结构,使用即存储入度也存储出度,这种表就叫做十字链表。 61 | 邻接多重表:由于遍历表时需要一些删除边操作而邻接表在删除边时非常麻烦,因此就设计出了邻接多重表 62 | 边集数组:由两个一维数组构成,一个存储顶点的消息,另一个存储边的信息(它的每个数据元素都由一条边的起点到终点的下标和权组成), 63 | 这种存储结构更侧重于边的相关操作(路径、路径长度、最短路径),而统计顶点的度需要扫描整个数组,效率不高。 64 | 65 | 图的遍历: 66 | 注意:图的遍历结果无论是深度优先还是广度优先,结果都不唯一 67 | 深度优先:类似树的前序遍历。 68 | 广度优先:类似树的层序遍历(需要借助队列) -------------------------------------------------------------------------------- /多路复用IO.c: -------------------------------------------------------------------------------- 1 | 2 | 一、IO模型介绍(读取数据的模式) 3 | 1、阻塞IO,常用的scanf、printf、read、write、cout、cin 4 | 2、非阻塞IO,recv、send和Qt中read、write 5 | 3、多路复用IO 6 | 4、信号驱动IO 7 | 5、异步IO 8 | 9 | 二、多路复用IO 10 | 在不创建新的进程和线程的情况下监控多个文件描述符,多应用于网络编程时一个服务端程序为多个客户端程序提供服务,多用于在业务逻辑 11 | 简单,客户端需要的服务时间短,响应时间无太高要求的场景。 12 | 13 | 三、使用select函数实现多路复用IO 14 | int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 15 | 功能:监控多个文件描述符的 读、写、异常 操作 16 | nfds:最大文件描述符+1 17 | readfds:监控读操作文件描述符集合 18 | writefds:监控写操作文件描述符集合 19 | exceptfds:监控异常操作文件描述符集合 20 | timeout:设置超时时间 21 | 返回值:监控到文件描述符的个数,超时返回0,出错返回-1 22 | 23 | void FD_CLR(int fd, fd_set *set); 24 | 功能:从集合中删除文件描述符 25 | int FD_ISSET(int fd, fd_set *set); 26 | 功能:测试集合中是否有文件描述符存在 27 | void FD_SET(int fd, fd_set *set); 28 | 功能:向集合中添加文件描述符 29 | void FD_ZERO(fd_set *set); 30 | 功能:清空文件描述符集合 31 | 32 | select设计不合理的地方: 33 | 1、所有被监视的文件描述符都需要检查(效率不高)。 34 | 2、每次调用select都需要向它传递新的监视对象信息 35 | select的优点是: 36 | 程序的兼容性高 37 | 38 | 39 | int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask); 40 | 功能:与select的功能大致类似 41 | 区别: 42 | 1、select函数用的timeout参数,是一个timeval的结构体(包含秒和微秒),然而pselect用的是一个timespec结构体(包含秒和纳秒) 43 | 2、select函数可能会为了指示还剩多长时间而更新timeout参数,然而pselect不会改变timeout参数 44 | 3、select函数没有sigmask参数,当pselect的sigmask参数为null时,两者行为时一致的。有sigmask的时候,pselect相当于如下的 45 | select()函数,在进入select()函数之前手动将信号的掩码改变,并保存之前的掩码值;select()函数执行之后,再恢复为之前的信号掩码值。 46 | 47 | 四、使用poll函数实现多路复用IO 48 | int poll(struct pollfd *fds, nfds_t nfds, int timeout); 49 | fds:所有被监控的文件描述符结构体数组 50 | nfds:数组的长度 51 | timeout:超时时间,毫秒单位 52 | 53 | struct pollfd 54 | { 55 | int fd; //被监控文件描述符 56 | short events; // 等待的需要监控事件 57 | short revents; // 实际发生了的事件,也就是返回结果 58 | }; 59 | events: 60 | POLLIN 普通或优先级带数据可读 61 | POLLPRI 高优先级数据可读 62 | POLLOUT 普通数据可写 63 | POLLRDHUP 对方socket关闭 64 | POLLERR 发生错误 65 | POLLHUP 发生挂起 66 | POLLNVAL 描述字不是一个打开的文件 67 | 68 | poll特点: 69 | 1.文件描述符没有最大限制 -数据结构:链表 70 | 2.每次调用都需要将fd集合从用户态拷贝到内核态 71 | 3.内核需要遍历所有fd,效率低 72 | 73 | 74 | 五、使用epoll函数实现多路复IO 75 | int epoll_create(int size); 76 | 功能:创建用于保存被监控文件描述符的空间 77 | int epoll_ctl(int epfd, int op, int fd, struct 78 | epoll_event *event); 79 | 功能:向文件中添加、删除,文件描述符 80 | int epoll_wait(int epfd, struct epoll_event *events, 81 | int maxevents, int timeout); 82 | 功能:监控文件描述符 83 | 84 | 85 | 1.文件描述符没有最大限制 --数据结构:红黑树 86 | 2.只需拷贝一次fd到内核态 87 | 3.内核只需判断就绪链表是否为空,不需要遍历所有fd,效率高,并把就绪fd拷贝到用户空间 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /排序.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ikaros-521/Learning-Notes/f6b00721f5e9adebefd0e9af674ac13c134eabd7/排序.jpg -------------------------------------------------------------------------------- /数据库.sql: -------------------------------------------------------------------------------- 1 | 一、数据库介绍 2 | 1、为什么使用数据库 3 | 计算机的资源有限,因此不可能把数据一直存储在内存中,而且内存一旦掉电数据就会不存在,我们需要所内存中的数据存储到文件中, 4 | 方便下次的编辑、拷贝。 5 | 但随着程序越来越复杂,数据也就会越来越庞大,管理这些数据就会成为编程中最难、最重要的问题。 6 | 使用文件保存不安全,可能会被误删,也可能被覆盖。 7 | 而数据库的访问是需要用户名、密码、相应的权限及命令。 8 | 文件读取数据并解析需要大量的重复代码,这种操作效率很低。 9 | 从数据库中查询数据只需要提供条件即可。 10 | 文件中删除一条记录异常麻烦,而且不同的程序对于文件的操作不同接口也不相似,因此访问文件的代码不能被复用。 11 | 对于这个问题就有人提出一个设想,统一文件的访问接口和访问方法,对文件进行统一管理,背后再增强对于数据管理、读取的优化操作, 12 | 使用对数据的管理、使用方便又快捷。 13 | 2、什么是数据库 14 | 数据库,顾名思义就是数据的仓库(电子文件柜),而对数据进行增加、删除、修改、查询等操作由数据库管理系统(DBMS)进行,对于 15 | 数据库进行设计、维护、管理的人员叫DBA。 16 | 3、数据库的类型 17 | 层次型数据库:数据的存储类似一棵树,以上下层级关系来组织数据。 18 | 网状型数据库:数据的存储类似一副图,各个数据节点和其他节点都连接关系。 19 | 关系型数据库:关系模型就是一张二维表,任何数据都可以通过行号和列确定,容易理解、使用方便。 20 | 4、目前主流的关系型数据库 21 | 商用的:Oracle、SQL Server、DB2 22 | 开源的:MySQL 23 | 桌面的:Access 24 | 嵌入式的:SQLite,适合于手机、桌面程序 25 | 26 | 二、SQL语言介绍 27 | 1、什么是SQL 28 | SQL是结构化查询语言的缩写,用来操作数据库,主要对数据用来增、删、改、查,高级操作是对数据库进行管理、维护。 29 | 目前所有的关系型数据库都支持SQL,因此我们只要学会SQL就能操作所有的数据库。 30 | 虽然SQL已经被ANSI组织定义为标准,但不幸的是每个数据库对标准的支持都不太一致,大多数的数据库都对SQL进行了扩展。 31 | 理论上SQL可以操作所有的数据库,但如果使用了某个数据库的扩展语句,换一个数据库后就不能再执行了。 32 | 但是SQL的核心功能绝大数数据库都是支持。 33 | 2、SQL语言的分类 34 | 数据定义语言:用于建立、修改、删除数据库对象 35 | CREATE 用于创建表或其他对象结构 36 | ALTER 用于修改表或其他对象结构 37 | DROP 用于删除表或其他对象 38 | TRUNCATE 用于删除表中的数据,保留表结构 39 | 40 | 数据操作语言:用于改变表中的数据 41 | INSERT 将数据插入到表中 42 | UPDATE 修改表中已经存在的数据 43 | DELETE 删除表中的数据 44 | 表中数据改变后需要执行事务控制语言才能所改变应用到数据库中。 45 | 46 | 事务控制语言:用来维护表中数据的一致性 47 | COMMIT 提交,确认已经进行修改的数据 48 | ROLLBACK 回滚,消除已经进行修改的数据改变 49 | SAVEPOINT 保存点,用于回滚操作 50 | 51 | 数据控制语言:用于执行权限的授予和回收操作 52 | GRANT 用于给用户或角色授予权限 53 | REVOKE 用于回收用户或角色的权限 54 | 55 | 数据查询语言 56 | SELECT 57 | 三、如何访问数据库 58 | 1、远程登录 59 | ubuntu下开启telnet 60 | sudo apt-get install openbsd-inetd 61 | sudo apt-get install telnetd 62 | sudo /etc/init.d/openbsd-inetd restart 63 | sudo netstat -a | grep telnet 检查是否安装成功 64 | telnet 192.168.0.121 在windows下测试 65 | 输入用户名 66 | 输入密码 67 | mysql -utest -p123456 68 | 69 | telnet 192.168.6.66 70 | sqlplus student/123456 进入Oracle数据库 71 | 72 | ubuntu下开启ssh 73 | sudo apt-get install openssh-server 74 | sudo ps -e |grep ssh 检查是否安装成功 75 | ssh zhizhen@192.168.0.121 在windows下测试 76 | 输入密码 77 | mysql -utest -p123456 78 | 2、客户端连接 79 | 连接名 自定义 80 | 用户名 student 81 | 密码 123456 82 | 保存口令 83 | 地址:192.168.6.66 84 | 端口:1521 85 | SID:orcl 86 | 87 | 四、SQL中的数据类型 88 | MySQL Oracle 89 | n/a | byte 单字节数据,布尔类型 90 | int | Number 整数 91 | float | Number 浮点型 92 | char | char 定长字符串 93 | varchar | varchar2 变长字符串 94 | date | date 日期 95 | 96 | char(n) 表示字符串长度 97 | char(10) 数据是“123”,依然存储10个字符 98 | 适合存储字符串长度波动不大的字符串 99 | 优点:是速度快,缺点:浪费空间 100 | varchar(n) 表示字符串长度 101 | varchar(10) 数据是“123”,依然存储4个字符 102 | 适合存储字符串长度不确定,波动比较大的字符串 103 | 优点:会根据字符串的长度进行存储,节约存储空间 104 | 缺点:与char相比速度稍慢 105 | Oracle中的Number(P,S) 106 | P表示总位数,S表示小数点后面的位数 107 | Number(4) 表示四整数 108 | Number(4,2) 小数后最多两位 109 | 110 | 五、数据定义语言 111 | 1、创建表 112 | CREATE TABLE 表名(字段名 字段类型,...); 113 | CREATE TABLE Student( 114 | name char(20), 115 | sex char(1), 116 | age int, 117 | id char(8)); 118 | 查表表结构:DESC 表名; 119 | 查看数据库中所有表: show tables; 120 | 121 | 2、修改表 122 | 1.修改表名 123 | RENAME TABLE 旧表名 TO 新表名; 124 | RENAME TABLE student TO Teacher; 125 | 126 | 2.增加列 127 | ALTER TABLE 表名 ADD (字段名 字段类型); 128 | ALTER TABLE Teacher ADD (id char(6)); 129 | 注意:只能增加到表的末尾,不能插入到中间。 130 | 131 | 3.删除列 132 | ALTER TABLE 表名 DROP 字段名; 133 | ALTER TABLE Teacher DROP sex; 134 | 135 | 4.修改列 136 | ALTER TABLE 表名 MODIFY id char(8); 137 | ALTER TABLE Teacher MODIFY id char(8); 138 | 139 | 3、删除表 140 | 1.删除表数据 141 | TRUNCATE 表名; 142 | 143 | 2.删除表,数据和结构都不存在 144 | DROP 表名; 145 | 146 | 六、数据操作语言 147 | 1、插入数据 148 | INSERT INTO 表名(字段名) VALUES(数据); 149 | INSERT INTO Student(name,sex,age,id) VALUES('hehehehehehe','m',18,1907123); 150 | 注意:SQL中可以使用''表示字符串 151 | 152 | 2、修改表数据 153 | UPDATE 表名 SET 字段名=数据,... where 条件; 154 | UPDATE Student SET sex='w',name='hehe' where not id=1907123; 155 | UPDATE Student SET age=17 where id=1907123 or id=1907124; 156 | 157 | 3、删除表数据 158 | DELETE FROM 表名 where 条件; 159 | DELETE FROM Student where id=1907123; 160 | 161 | 七、事务控制语言 162 | 注意:数据定义语言立即有效,不能进行事务控制,只有数据操作语言才能进行事务控制。 163 | 1、COMMIT 提交 164 | 在Oracle数据库,一个用户向表中插入数据(他自己能够查询到),其它用户并不能立即查询到,只有执行了 COMMIT 命令后其他用户才能 165 | 查询到。 166 | INSERT INTO Student(name,sex,age,id) VALUES('xixi','w',20,1907125); 167 | COMMIT; 168 | 169 | 在MySQL数据库中,默认设置的 autocommit 一个用户插入的数据,其他用户也能立即查看到。 170 | set session autocommit=0; --开启当前连接的自动提交 171 | set global autocommit=0; --开启所有连接的自动提交,需要root权限 172 | set session autocommit=1; --关闭当前连接的自动提交 173 | 174 | 2、回滚 175 | 当使用数据操作语言对数据进行更改后,如果没有进行COMMIT,那么就可以撤销(反悔),使用 ROLLBACK 命令,可以把数据还原到数据更改 176 | 前的样子。 177 | 178 | 3、保存点 179 | 如果向表中插入了四条记录,此时如果直接rollback,则四条记录都会消失,如果只想保留一条、两条或三条,需要在每插入一个记录后设置 180 | 一个保存点,使用 rollback to savepoint 保存点;可以回到任意一条记录后。 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | --CREATE TABLE Teacher(id int,name char(20),subject char(20),salary float,start_time date,birth date); 207 | 208 | CREATE TABLE Teacher(id int,name char(20),subject char(20),salary float); 209 | INSERT INTO Teacher VALUES(1001,'mage1','S1',6666.666); 210 | INSERT INTO Teacher VALUES(1002,'mage2','S2',6677.666); 211 | INSERT INTO Teacher VALUES(1003,'mage3','S3',6688.666); 212 | INSERT INTO Teacher VALUES(1004,'mage4','S4',6699.666); 213 | INSERT INTO Teacher VALUES(1005,'mage5','S5',1010.666); 214 | INSERT INTO Teacher VALUES(1006,'mage6','S6',1111.666); 215 | INSERT INTO Teacher VALUES(1001,'mage1','S1',6666.666); 216 | INSERT INTO Teacher VALUES(1007,'mage7','S1',1212.666); 217 | INSERT INTO Teacher VALUES(1008,'age','S1',6611.666); 218 | INSERT INTO Teacher VALUES(1009,'bge','S1',6622.666); 219 | 220 | CREATE TABLE Employee(id int,name char(20),post char(20),base_sal float,royalty float); 221 | INSERT INTO Employee VALUES(19001,'xiu','SSS',6000,1.6); 222 | INSERT INTO Employee VALUES(19002,'xie','SS',5500,1.4); 223 | INSERT INTO Employee VALUES(19003,'pang','SSSSS',10000,1.8); 224 | INSERT INTO Employee(id,name,post,base_sal) VALUES(19004,'sun','A',15000); 225 | INSERT INTO Employee VALUES(19005,'xiu1','SSS',5000,1.4); 226 | INSERT INTO Employee VALUES(19006,'xiu2','SS',7000,1.2); 227 | INSERT INTO Employee VALUES(19007,'xiu3','SSS',4500,1.8); 228 | INSERT INTO Employee VALUES(19008,'xiu4','SSS',4500,1.6); 229 | INSERT INTO Employee VALUES(19009,'xiu4','SSS',4500,1.9); 230 | 231 | CREATE TABLE emp(id int,dept_id int,name char(20),post char(20),base_sal float); 232 | INSERT INTO emp VALUES(1001,101,'bob','S1',5000.0); 233 | INSERT INTO emp VALUES(1002,101,'sam','S2',5000.0); 234 | INSERT INTO emp VALUES(1003,102,'tom','S3',6000.0); 235 | INSERT INTO emp VALUES(1004,102,'john','S4',6000.0); 236 | INSERT INTO emp VALUES(1001,103,'mery','S5',7000.0); 237 | INSERT INTO emp VALUES(1001,103,'jams','S6',7000.0); 238 | 239 | CREATE TABLE dept(id int,name char(20),addr char(20)); 240 | INSERT INTO dept VALUES(101,'info','hangzhou'); 241 | INSERT INTO dept VALUES(102,'tele','shanghai'); 242 | INSERT INTO dept VALUES(103,'pers','beijing'); 243 | 244 | 一、数据查询语言 245 | 1、查询表中 246 | SELECT 字段1,字段2... FROM 表名; 247 | 例:查询姓名与年龄 248 | SELECT name,age FROM Student; 249 | * 在SQL中也是通配符,代表所有字段 250 | 2、SQL中的数学运算 + - * / 251 | 例:查询每个教师的日薪,按每月30天计算 252 | SELECT name,salary/30 FROM Teacher; 253 | 例:查询每个教师的年薪 254 | SELECT name,salary*12 FROM Teacher; 255 | 3、字段别名 256 | 为查询结果新取一个字段名 257 | 例:查询每个教师的年薪,并为查询结果设置的字段名为yearsal 258 | SELECT name,salary*12 yearsal FROM Teacher; 259 | 4、字符串操作 260 | Oracle中使用 || 连接字符串,而在MySQL它逻辑或运算。 261 | CONCAT(s1,s2,...) 262 | 返回连接参数产生的字符串,一个或多个待拼接的内容,任意一个人为NULL则返回值为NULL。 263 | 例:显示所有教师名和所教科目中间有下划线连接 264 | SELECT concat(name,'_',subject) FROM Teacher; 265 | 266 | CONCAT_WS(x,s1,s2,...) 267 | 返回多个字符串拼接之后的字符串,每个字符串之间有一个x。 268 | 例:连接字符串,并使用空格分隔。 269 | SELECT CONCAT_WS('_',name,subject,'HH') FROM Teacher; 270 | 271 | SUBSTRING(s,n,len)、MID(s,n,len) 272 | 两个函数作用相同,从字符串s中返回一个第n个字符开始、长度为len的字符串。 273 | 例:获取mage 编号 274 | SELECT SUBSTRING(NAME,5,1) FROM Teacher; 275 | SELECT MID(NAME,5,1) FROM Teacher; 276 | 277 | LEFT(s,n)、RIGHT(s,n) 278 | 前者返回字符串s从最左边开始的n个字符,后者返回字符串s从最右边开始的n个字符。 279 | 例:获取小马哥名字,不要编号 280 | SELECT LEFT(name,4) FROM Teacher; 281 | 282 | INSERT(s1,x,len,s2) 283 | 返回字符串s1,其子字符串起始于位置x,用字符串s2取代s1的len个字符。 284 | "12345678" 3,2,"ABCDE" 285 | SELECT INSERT('12345678',3,2,'ABCDEF'); 286 | 287 | REPLACE(s,s1,s2) 288 | 返回一个字符串,用字符串s2替代字符串s中所有的字符串s1。 289 | SELECT REPLACE('12345678','345','ABC'); 290 | 291 | LOCATE(str1,str)、POSITION(str1 IN str)、INSTR(str,str1) 三个函数作用相同,返回子字符串str1在字符串str中的开始位置 292 | (从第几个字符开始)。 293 | SELECT LOCATE('123','ABCDE123DEFWE'); 294 | 295 | FIELD(s,s1,s2,...) 返回第一个与字符串s匹配的字符串的位置。 296 | 297 | SELECT FIELD('123','ABC','DEF','123','ADW'); 298 | 299 | 重点:SQL语言中下标从1开始 300 | 301 | 5、排重显示 302 | SELECT DISTINCT 字段 FROM 表名; 303 | 查询:学校一共开设多少科目 304 | SELECT DISTINCT subject FROM Teacher; 305 | 306 | 6、条件查询 307 | SELECT 字段 FROM 表名 WHERE 条件; 308 | 当WHERE条件为真时显示相关数据,配合相关比较运算符。 309 | > < >= <= = != 310 | 例:查询所有教S1科目的老师 311 | SELECT * FROM Teacher WHERE subject = 'S1'; 312 | 例:查询所有不教S1科目的老师 313 | SELECT * FROM Teacher WHERE subject != 'S1'; 314 | SELECT * FROM Teacher WHERE not subject = 'S1'; 315 | 例:查询所有年薪超过8w的老师 316 | SELECT name,id,salary*12 FROM Teacher WHERE salary*12>=80000; 317 | 318 | 注意:SQL中字符串可以直接使用 比较运算符,与strcmp比较规则一致。 319 | SELECT * FROM Teacher WHERE name > 'mage5'; 320 | 321 | SELECT * FROM 表名 WHERE x BETWEEN n AND m; 322 | 显示x值 在n到m之间的数据 323 | 例:查询月薪在6667,6688之间的教师信息 324 | SELECT * FROM Teacher WHERE salary BETWEEN 6667 AND 6688; 325 | SELECT * FROM Teacher WHERE salary>6667 AND salary<6688; 326 | 327 | SELECT * FROM 表名 WHERE x in(n1,n2,n3); 328 | 显示x值在(n1,n2,n3,...)列表中的相关数据 329 | 例: 330 | SELECT * FROM Teacher WHERE subject in('S1','S4','S5'); 331 | 332 | SELECT * FROM 表名 WHERE x LIKE str; 333 | SQL中字符串可以进行模糊查询,str中可以使用通配符 334 | %代表任意多个字符 _代表一个字符 335 | 例:查询名字mage开头的相关老师信息 336 | SELECT * FROM Teacher WHERE name LIKE 'mage%'; 337 | 例:查询名字以1结尾的相关老师信息 338 | SELECT * FROM Teacher WHERE name LIKE '%1'; 339 | 例:查询名字以ge和任意一字符结尾的相关老师信息 340 | SELECT * FROM Teacher WHERE name LIKE '%ge_'; 341 | 342 | 注意:在SQL中也可使用逻辑运算,AND OR NOT(&& || !不一定支持) 343 | 344 | 7、空值处理 345 | 在SQL中空值是一种特殊数据,任何数据与空值进行计算结果为空。 346 | IFNULL(x,n) 如果x的值为空,则用n来替换(MySQL,Oracle使用nvl函数)。 347 | 例:查询员工表中的年薪 348 | SELECT name,base_sal*IFNULL(royalty,1)*12 FROM Employee; 349 | 例:查询有年终奖的员工 350 | SELECT * FROM Employee WHERE royalty IS NULL; 351 | 例:查询有年终奖的员工 352 | SELECT * FROM Employee WHERE royalty IS NOT NULL; 353 | 354 | 8、排序 355 | 是对查询结果进行排序,而不是对数据库中表的数据进行排序。 356 | SELECT 数据 FROM ORDER BY 字段 类型; 357 | 默认升序,DESC 降序 358 | 例:按基本工资进行排序 359 | SELECT * FROM Employee ORDER BY base_sal; 360 | 例:按年终奖系数进行降序 361 | SELECT * FROM Employee ORDER BY royalty desc; 362 | 例:基本工资为第一个排序字段,年终奖系数为第二排序字段 363 | SELECT * FROM Employee ORDER BY base_sal,royalty; 364 | 365 | 9、单行函数 366 | 对表中的数据每处理一行就返回一个结果,这种函数叫单行函数。 367 | UPPER 小写转大写 368 | SELECT id,UPPER(name) FROM Employee; 369 | LOWER 大写转小写 370 | SELECT id,UPPER(name),LOWER(post) FROM Employee; 371 | INITCAP 首字母大写(MySQL不支持) 372 | SELECT id,INITCAP(name) FROM Employee; 373 | LENGTH 计算字符串长度 374 | SELECT id,LENGTH(name) FROM Employee; 375 | to_char 其他类型的数据转换成字符串(MySQL不支持) 376 | 377 | 10、组函数 378 | 一次查询只得到一个结果,如果在数据进行分组的情况下,一个分组得到一个结果。 379 | COUNT 计数 380 | 例:统计101部门的员工数量 381 | SELECT COUNT(id) FROM emp WHERE dept_id=101; 382 | 383 | MAX 求最大值 384 | 例:求基本工资最高值 385 | SELECT MAX(base_sal) FROM Employee; 386 | 387 | MIN 求最小值 388 | 例:求基本工资最低值 389 | SELECT MIN(base_sal) FROM Employee; 390 | 391 | SUM 求和 392 | 例:求基本工资总支出 393 | SELECT SUM(base_sal) FROM Employee; 394 | 395 | AVG 求平均值 396 | 例:求员工平均工资 397 | SELECT AVG(base_sal) FROM Employee; 398 | 399 | 11、多表查询 400 | 根据员工表,部门表查询出每个员工的上班地点。 401 | SELECT emp.name,addr FROM emp,dept WHERE dept_id=dept.id; 402 | 403 | 12、分组查询 404 | SELECT 组函数(字段) FROM 表名 GROUP BY 分组条件; 405 | 例:每个部门的平均工资 406 | SELECT dept.name,AVG(base_sal) FROM emp,dept WHERE emp.dept_id=dept.id GROUP BY dept_id; 407 | 408 | HAVING 筛选分组后的数据 409 | SELECT 组函数(字段) FROM 表名 GROUP BY 分组条件 HAVING 筛选; 410 | 例:显示部门的平均工资>6000的部门 411 | SELECT dept.name,AVG(base_sal) FROM emp,dept WHERE emp.dept_id=dept.id GROUP BY dept_id HAVING AVG(base_sal) > 6000; 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | CREATE TABLE Emp(id int,dept_id int,mgr_id int,name char(20),post char(20),salary float,start_time date,birth date); 440 | INSERT INTO Emp VALUES(2019003,1001,2019001,'sam','P1',5000,STR_TO_DATE('2019-9-16','%Y-%m-%d'),STR_TO_DATE('1999-9-16','%Y-%m-%d')); 441 | INSERT INTO Emp VALUES(2019004,1001,2019003,'bob','P3',5500,sysdate(),STR_TO_DATE('1999-9-16','%Y-%m-%d')); 442 | 443 | 444 | 445 | 13、日期数据 446 | 注意:数据库中的日期是一种特殊类型,需要一些函数进行转换。 447 | Oracle数据库 448 | TO_DATE(date_str,format_str) 把字符串按照一定的格式转换成日期数据 449 | TO_DATE('2019-5-5','yyyy-mm-dd') 450 | TO_CHAR(date,format_str) 把日期数据按照一定的格式转换成字符串 451 | TO_CHAR(date,'yyyy-mm-dd') 452 | 453 | yyyy 四位年份 454 | yy 两位年份 455 | mm 整数月份 456 | mon 月份简拼 457 | month 月份全拼 458 | dd 整数天数 459 | dy 周几简拼 460 | day 周几全拼 461 | hh12 12小时制 462 | hh24 24小时制 463 | mi 分钟 464 | ss 秒 465 | 466 | MySQL数据库 467 | STR_TO_DATE(str,format) 把字符串按照一定的格式转换成日期数据 468 | DATE_FORMAT(date,format) 把日期数据按照一定的格式转换成字符串 469 | 470 | %Y 四位年份 471 | %y 两位年份 472 | %M 月份全拼 473 | %m 整数月份 474 | %D 带英文后缀的天数 475 | %d 整数天数 476 | 477 | sysdate() 获取当前系统时间 478 | 479 | 14、子查询 480 | 把一个查询的结果当作另一个查询的数据源 481 | 例:查询员工表汇中谁是领导 482 | SELECT empno,ename FROM emp WHERE empno IN (SELECT DISTINCT mgr FROM emp); 483 | 例:查询普通员工信息 484 | SELECT empno,ename FROM emp WHERE empno NOT IN (SELECT DISTINCT mgr FROM emp WHERE mgr NOT IS NULL); 485 | 注意:空值能和任意数据匹配 486 | 487 | 一、数据约束 488 | 约束:对表中的数据加以限制,不允许非法数据插入 489 | 注意:MySQL数据库不能很好的支持约束条件 490 | 主键:非空且唯一 491 | PRIMARY KEY 492 | CREATE TABLE student(id int PRIMARY KEY,name char(20),sex char); 493 | INSERT INTO student VALUES(1,'TOM','M'); 494 | 495 | 唯一:数据不能重复 496 | UNIQUE 497 | CREATE TABLE student2(id int PRIMARY KEY,name char(20) UNIQUE,sex char); 498 | INSERT INTO student2 VALUES(1,'TOM','M'); 499 | 500 | 非空:数据不能为空,插入时不能省略 501 | NOT NULL 502 | CREATE TABLE student3(id int PRIMARY KEY,name char(20) UNIQUE,sex char NOT NULL); 503 | INSERT INTO student3 VALUES(1,'TOM','M'); 504 | 505 | 检查:设置检查条件 506 | CHECK 507 | CREATE TABLE student4(id int PRIMARY KEY,name char(20) UNIQUE,sex char NOT NULL,age int CHECK(age > 8)); 508 | INSERT INTO student4 VALUES(1,'TOM','M',7); 509 | 510 | 外键:与主键配合使用,用于表连接,外键的数据必须要在主键中能够找到。 511 | FOREIGN KEY 512 | 513 | 先主键 514 | CREATE TABLE student(id int PRIMARY KEY,name char(20),sex char); 515 | INSERT INTO student VALUES(2,'mery','w'); 516 | 后外键 517 | CREATE TABLE report( 518 | id int, 519 | mr float, 520 | cr float, 521 | er float, 522 | CONSTRAINT fk_id FOREIGN KEY(id) REFERENCES student(id) 523 | ); 524 | 525 | INSERT INTO report VALUES(1,80,90,100); 526 | 此时在插入数据时,必须要确定主外键字段的值在主键中能够找到。 527 | 注意:主键在删除数据时,必须把外键对应的数据先删除,才能删除主键。 528 | ON DELETE CASCADE 529 | CREATE TABLE report( 530 | id int, 531 | mr float, 532 | cr float, 533 | er float, 534 | CONSTRAINT fk_id FOREIGN KEY(id) REFERENCES student(id) ON DELETE CASCADE 535 | ); 536 | 537 | 二、视图 538 | 视图就是一张虚拟的表,它的本质是一个SQL语句。 539 | 视图的优点就是使用方便,还不占用多余存储空间,缺点是效率低,不适合长期频繁查询。 540 | CREATE VIEW 视图名 as