经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
当初自学C++时的笔记记录
来源:cnblogs  作者:Maicss  时间:2021/4/6 10:17:59  对本文有异议

编辑:刘风琛

最初编写日期:2020年4月11日下午 最新更新日期:2020年9月20日上午

标注:

  • 从笔记开始截止到程序第四章“程序流程结构”,使用Joplin编写,其余部分为Typora编写。
  • 笔记对应课程链接为:(https://www.bilibili.com/video/BV1et411b73Z) 作者:黑马程序员-
  • 当前进度为P107:类和对象

目录

1. 变量

给一段内存起名,方便使用。

2. 常量

用于记录程序中不可更改的数据。

  • 定义常量的两种方式
    1. #define宏常量 #define 常量名 常量值
      • 通常在文件上方定义,表示一个常量。
    2. const修饰的变量 const 数据类型 常量名=常量值
      • 通常在变量定义前加const,修饰该变量为常量,不可更改。`

3. 数据类型

sizeof()可以返回当前数据类型所占内存大小。

  • 强制转换
    语法:(数据类型)被转变量
    举例:

    1. int main(){
    2. char ch = 'a';
    3. cout<<(int)ch<<endl;
    4. return 0;
    5. }

    输出结果:97(字符a的ASCII码)

  • 转义字符
    转义字符用反斜杠\表示,可以用来表示ASCII码的特殊值。

转义字符 含义 ASCII码值
\a 警报 007
\b 退格(BS),将当前位置移到前一列 008
\f 换页(FF),将当前位置移到下页开头 012
\n 换行(LF),将当前位置移到下一行开头 010
\r 回车(CR),将当前位置移到本行开头 013

3.1 整型

C++中可以用以下方式表示整型,区别在于所占内存空间不同

数据类型 占用空间 取值范围
short(短整型) 2字节 -215~215-1
int(整型) 4字节 -231~231-1
long(长整型) windows为4字节;Linux 32位4字节,64位8字节 -231~231-1
long long(长长整型) 8字节 -263~263-1

3.2 浮点型(小数)

浮点型分为以下两种:

数据类型 占用空间 取值范围
float(单精度) 4字节 7位有效数字
double(双精度) 8字节 15~16位有效数字
  • 科学计数法
    举例:
    整数:3e1表示3*10^1,也就是30
    小数:3e-1表示3*10^-1,也就是0.3

3.3 字符型

表示单个字符的数据类型,只占一个字节。

  • 语法:char ch = 'a'
  • 注意:
    • 字符需要用单引号括起。
    • 且单引号中只能有一个字符。
    • 计算机真正存放的不是字符,是ASCII码。

3.4 字符串

表示一串字符,可以有两种表示方式。

  • C语言中常用方式(数组):char 变量名[] = "abcde"
    示例:

    1. int main(){
    2. char str[] = "Hello world!";
    3. cout<<str<<endl;
    4. return 0;
    5. }
    • 注意:
      1. 字符串内容要用单引号括起来。
      2. 变量名后必须加中括号表示数组。
  • 当前标准方式:string 变量名 = "abcde"
    示例:

    1. #include <string>
    2. int main(){
    3. string str = "Hello World!";
    4. cout<<str<<endl;
    5. return 0;
    6. }
    • 注意:
      1. 使用string需要引入头文件:#include <string>

3.5 布尔类型

代表"true(1)"或者"false(0)",表示逻辑。

  • 所占内存:1字节。
  • 本质上1代表真,0代表假。
  • 使用cin输入时,非0表示真,0表示假,0~1之间的小数视为0。

3. 运算符

包括四则运算,取余等方法。

  • 四则运算注意事项

    • 除法符号为"/"注意不要和反斜杠"\"混淆。
    • 除法运算时,两个整数(这里指类型)相除,结果依然是整数,小数部分消除(不是四舍五入)。
    • 0为除数时程序崩溃
  • 取模运算

    • 符号为"%"
    • 取模运算作用是获取两数相除所得余数。
    • 取模运算本质上也是除法的一种,除数不可为0。
    • 小数不可以进行取模运算
  • 递增和递减

    • 两者的功能类似,都是让变量加、减1
    • 前置递增/递减为先加1,后运算;后置递增/递减为先运算,后加1.
  • 赋值运算符

    • 包括=+=-=*=/=%=
      示例:
      1. int main(){
      2. int a=1;
      3. a=3;
      4. //此时a=3;
      5. a+=2;
      6. //此时a=5
      7. a-=3;
      8. //此时a=2
      9. a*=2;
      10. //此时a=4
      11. a/=2;
      12. //此时a=2
      13. a%=1;
      14. //此时a=0
      15. cout<<a<<endl;
      16. return 0;
      17. }
  • 比较运算符

  • 包括==>=<=!=><

  • 逻辑运算符

    • 包括非!、与&&、或||
  • 三目运算符

    • 用法:表达式1 ? 表达式2 : 表达式3
    • 含义:如果表达式1成立,则返回表达式2的运行结果,否则返回表达式3的运行结果。
      示例:
    1. int main(){
    2. int a=1,b=10,c=0;
    3. //用法一
    4. c = (a > b ? a : b); //括号能提高三目运算的优先级防止运行出错
    5. //将a和b中值较大的赋值给c
    6. //用法二
    7. (a > b ? a : b) = 999;
    8. //把999赋给a和b中较大的变量
    9. return 0;
    10. }

4. 程序流程结构

4.1 顺序结构

就是从头到尾顺序执行,没啥可记的。

4.2 选择结构

判断选择,可以实现跳过或者分支。

  • if语句
    用法一:if(条件){满足条件执行的代码块}

    • 注意:
      1. 注意不要加入多余的分号。
    • 示例:
    1. int main(){
    2. int score;
    3. cout<<"Please input your score:";
    4. cin>>score;
    5. if (score>=600){
    6. cout<<"Good!";
    7. }
    8. return 0;
    9. }

    用法二:if(条件){满足条件执行的代码块}else{不满足时执行的代码块}

    • 注意:
      1. else为可选分支,删除后和用法一相同。
      2. 注意不要加入多余的分号。
    1. int main(){
    2. int score;
    3. cout<<"Please input your score:";
    4. cin>>score;
    5. if (score>=600){
    6. cout<<"Good!";
    7. }else{
    8. cout<<"Bad!";
    9. }
    10. return 0;
    11. }

    用法三:if(条件1){满足条件执行的代码块}else if(条件2){不满足条件1但满足条件2时执行的代码块}
    -。

    1. elseelse if 为可选分支.
    2. else if可并列多次使用
    1. int main(){
    2. int score;
    3. cout<<"Please input your score:";
    4. cin>>score;
    5. if (score>=600){
    6. cout<<"Good!";
    7. }
    8. else if(score>=400){
    9. cout<<"Bad!";
    10. }
    11. else{
    12. cout<<"So Bad!";
    13. }
    14. return 0;
    15. }

    用法四:if语句的嵌套,我认为没啥高级的,所以不记了。

  • switch语句

    • 用法:在示例中演示
    • 意义:可以轻松实现多分支
      示例:
    1. int main(){
    2. int level;
    3. cin>>level;
    4. switch(level){
    5. case 1:
    6. cout<<"Good";
    7. break;
    8. case 2:
    9. cout<<"Normal";
    10. break;
    11. case 3;
    12. cout<<"Bad";
    13. break;
    14. default :
    15. cout<<"非法输入!请输入1~3之间的整数。";
    16. break;
    17. }
    18. return 0;
    19. }
    • 注意:
      1. 需要使用break跳出分支。
      2. 缺点是无法使用区间视线分支。

4.3 循环结构

循环执行代码块。

4.3.1 while语句

条件满足时不断循环执行指定代码块,否则跳出循环。

  • 用法:while(条件){条件为真时循环执行的代码块}

  • 注意:

    1. 可以使用break跳出循环。
  • 示例:

    1. int main(){
    2. int a=0;
    3. while(a<10){
    4. a++;
    5. cout<<a;
    6. }
    7. return 0;
    8. }

4.3.2 do...while语句

  • 用法:do{代码块}while(条件);

  • 注意:

    1. 基本注意事项和while相同。
  • 示例:

    1. int main(){
    2. int a=0;
    3. do{
    4. a++;
    5. cout<<a<<endl;
    6. }
    7. while(a<10);
    8. return 0;
    9. }

4.3.3 for循环语句

  • 用法:for(起始表达式;条件表达式;末尾表达式){循环代码块}

  • 注意:可以使用break跳出循环。

  • 示例:

    1. int main(){
    2. for(int i=1;i<10;i++){
    3. cout<<i<<endl;
    4. }
    5. return 0;
    6. }

4.4 跳转语句

用于跳出或者移动当前结构中的运行位置。

4.4.1 break语句

  • 可以出现在switch语句中,用于跳出分支。
  • 可以出现在循环语句中,用于跳出循环。
  • 在位于嵌套循环结构时,用于跳出当前所在层的循环。

4.4.2 continue语句

  • 作用:在循环语句中跳过余下尚未执行的语句,直接进入下一次循环。

  • 示例:

    1. int main(){
    2. for (int i=1;i<=10;i++)
    3. {
    4. cout<<i<<endl;
    5. continue;
    6. cout<<"这段不被输出\n";
    7. }
    8. return 0;
    9. }

4.4.3 goto语句

  • 作用:可以跳转到任意标记的位置。

  • 示例:

    1. int main(){
    2. for(int i=1;i<=10;i++){
    3. cout<<"这是第一句话\n";
    4. cout<<"这是第二句话\n";
    5. goto flag;
    6. cout<<"这句话我们不要了\n";
    7. flag:
    8. cout<<"这是第三句话\n";
    9. }
    10. return 0;
    11. }

5. 数组

数组就是一个集合,里边存放了一组相同类型的数据。

  • 特点
    1. 数组中每个元素都是相同的数据类型。
    2. 数组是由连续的内存位置组成的。

5.1 一维数组

  • 一维数组的定义方式:

    1. 数据类型 数组名[数组长度];
    2. 数据类型 数组名 [数组长度]={值1,值2,...,值n};
    3. 数据类型 数组名[]={值1,值2,...,值n};
  • 访问格式:array [0]

  • 注意事项:

    1. 访问时下标从0开始。
  • 示例:

    1. int main(){
    2. int arr[5]={1,2,3,4};//只初始化了前四个,第五个值默认初始化为0
    3. for(int i=0;i<=4;i++)
    4. {
    5. cout<<arr[i]<<endl;
    6. }
    7. arr[4]=5; //这是对数组中未初始化的第5个值赋值
    8. cout<<arr[0];
    9. return 0;
    10. }
  • 补充:

    • 数组名的用途:
      1. 可以统计数组或数组中元素所占内存空间。(使用sizeof(数组名/数组名[])函数)
      2. 可以获取数组在内存中的首地址。(cout<<array;
    • 数组名为常量,不可直接赋值。
  • 一维数组的倒置示例:

    1. int main(){
    2. int arr [5]={1,2,3,4,5};//创建一个数组
    3. int temp,a,b;
    4. a=0;b=sizeof(arr)/sizeof (arr[0])-1;//b为通过计算得出的数组中元素数量减1
    5. while(a<b){
    6. temp=arr[a];
    7. arr[a]=arr[b];
    8. arr[b]=temp;
    9. a++;b--;
    10. }
    11. b=sizeof(arr)/sizeof (arr[0]);//为了节省内存,将b重置为数组元素个数
    12. for(int i = 1;i<=b;i++){ //循环b次,依次输出数组中每个元素的值
    13. cout<<arr[i-1]<<endl;
    14. }
    15. return 0;
    16. }
  • 一维数组的顺序排列示例(冒泡排序):

    1. int main(){
    2. int arr[5]={1,5,2,3,4};
    3. for (int i = 0;i < 5;i++){
    4. for(int j = 0;j<(5-i-1);j++){
    5. if (arr[j]>arr[j+1]){
    6. int temp;
    7. temp=arr[j];
    8. arr[j]=arr[j+1];
    9. arr[j+1]=temp;
    10. }
    11. }
    12. }
    13. for (int q = 1; q<=5;q++){
    14. cout<<arr[q-1]<<endl;
    15. }
    16. return 0;
    17. }

5.2 二维数组

  • 定义方式:

    1. 数据类型 数组名 [行数][列数]={{数值1,数值2...},{数值n,数值n+1...}};
    2. 数据类型 数组名 [行数][列数];
    3. 数据类型 数组名 [行数][列数]={数据1,数据2,数据3,数据4};
    4. 数据类型 数组名 [][列数]={数据1,数据2,数据3,数据4};
    • 示例:

      1. int main(){
      2. //这是创建二维数组最直观的形式
      3. int arr[2][3]={
      4. {1,2,3},
      5. {4,5,6}
      6. };
      7. //使用for循环嵌套遍历输出二位数组中每个元素
      8. for (int i=0;i<2;i++){
      9. for (int j=0;j<3;j++){
      10. cout<<arr[i][j]<<" ";
      11. }
      12. cout<<endl;
      13. }
      14. return 0;
      15. }
  • 二维数组名的用途

    1. 可以统计数组、数组中一行或数组中元素所占内存空间。(使用sizeof(数组名/数组名[]/数组名[][])
    2. 可以获取数组在内存中的首地址。(cout<<array;
  • 注意:

    1. 为了程序的可读性,我们一般使用前两种定义方式。
    2. 第三种定义方式会自动分出行列。
    3. 第四种必须指定列数,行数会依据数据数量进行自动分配。

6. 函数

函数就是一个程序块,可以方便的进行调用,能很好的减少代码量,一个较大的程序往往分成好多模块,每个模块实现特定功能。

6.1函数的定义

  • 函数的定义示例:

    1. 返回值类型 函数名(形参)
    2. {
    3. 程序代码块;
    4. retuen 返回值表达式;
    5. }
  • 注意:

    1. 必须返回一个正确的返回值类型。
    2. 若不需要返回值可以声明void函数。

6.2 函数的调用

  • 语法:函数名(参数)
  • 形式参数也叫形参,是一个形式,调用的是使用函数时传递的实参
  • 形参的值在函数中发生变化不会影响到实参。

6.3 函数的声明

  • 语法返回值类型 函数名(形参)

  • 注意:

    • 在main函数前声明函数防止程序运行时无法正常调用函数。
    • 可以有多次声明,但是只能有一次定义
  • 示例:

    1. int max(int num1,int num2); //函数max的声明
    2. int main(){
    3. cout<<max(100,101)<<endl;
    4. return 0;
    5. }
    6. int max(int num1,int num2){ //函数的定义
    7. return num1 > num2 ? num1:num2;
    8. }
    9. //函数定义在main函数后需要在main函数前声明。

6.4 函数的分文件编写

为了防止单文件形势下代码量过大。

  • 要素:

    1. 一个自定义的头文件(.h)
    2. 源文件(.cpp)
    3. 函数的声明写在头文件中
    4. 函数的定义写在源文件中
  • 示例:

    • 文件结构:

    • 代码示例:

      • main.cpp

        1. #include <iostream>
        2. #include "max.h"
        3. using namespace std;
        4. int main(){
        5. cout<<max(100,101)<<endl;
        6. return 0;
        7. }
      • max.h

        1. #include <iostream>
        2. using namespace std;
        3. int max(int num1,int num2);
      • max.cpp

        1. #include "max.h"
        2. int max(int num1,int num2){
        3. return num1 > num2 ? num1:num2;
        4. }

        输出结果:100

6.5 函数的默认值

  • 语法:返回值类型 函数名(参数名=默认值);

  • 注意:

    • 如果某个位置开始有默认参数,那么从该位置往后都应该有默认参数。
    • 声明和实现只能有一个设置默认参数,不允许重定义默认参数。
  • 示例:

    1. void print(int a=10,int b=20,int c=30){ //给所有选项都设置了默认参数
    2. cout<<a+b+c<<endl;
    3. }
    4. int main()
    5. {
    6. print(1,2,3);//调用函数是传递参数
    7. //输出6
    8. print();//调用函数时不传递参数,使用默认参数
    9. //输出60
    10. return 0;
    11. }

6.6 函数的占位参数

  • 语法:返回值类型 函数名 (数据类型);

  • 示例:

    1. void print(int = 10){ //只有数据类型,没有变量名就是占位参数
    2. cout<<"Hello World!";
    3. }
    4. int main(){
    5. print(); //因为占位参数具有默认值,所以此处无需传递,否则必须传递一个相应类型的参数
    6. }

6.7 函数的重载

6.7.1 概述

  • 意义:函数名可以相同,提高函数复用性。

  • 条件:

    • 同一作用域下。
    • 函数名称相同。
    • 函数参数名参数个数参数顺序不同。
  • 注意:函数的返回值不可用作函数重载的条件。

  • 示例:

    1. void print(){
    2. cout<<"print()函数被调用\n";
    3. }
    4. void print(int a){
    5. cout<<"print(int a)函数被调用\n";
    6. }
    7. int main()
    8. {
    9. print();//print()函数被调用
    10. print(1);//print(int a)函数被调用
    11. //其他例如参数名,参数个数,参数顺序不同 同理
    12. return 0;
    13. }

6.7.2 函数重载的细节问题

  • 常量引用

    • 示例:

      1. void print(int& a){
      2. cout<<"print()函数被调用\n";//如果传入数字1,则为int& a = 1 不合法
      3. }
      4. void print(const int& a){ //相当于const int& a = 1 合法
      5. cout<<"print(int a)函数被调用\n";
      6. }
      7. int main()
      8. {
      9. int a=1;
      10. print(a);
      11. print(1);
      12. return 0;
      13. }
  • 引入默认值导致的二义性

    • 示例:

      1. void print(int a){
      2. cout<<"print()函数被调用\n";
      3. }
      4. void print(int a,int b=1){
      5. cout<<"print(int a,int b=1)函数被调用\n";
      6. }
      7. int main()
      8. {
      9. print(1);//这个会报错,因为产生了二义性
      10. print(1,1);//会调用print(int a,int b=1)函数
      11. return 0;
      12. }

7. 指针

可以通过指针间接访问内存地址。

  • 注意:
    • 内存编号从0开始记录,一般使用十六进制数字保存
    • 可以利用指针变量保存地址
    • 不管什么指针,在32位系统下占用4字节,64位占用8字节。

7.1 指针的定义

  • 语法:数据类型 * 指针变量名

7.2 指针的使用

  • 让指针记录一个地址:

    • 语法:指针变量名 = &变量名

    • 注意:

      • &为取址符,可以获取当前内存地址。
  • 指针指向函数:

    • 语法:返回值 (*指针名)(参数列表);
  • 指针的使用:

    • 通过解引用影响指针指向内存区域所储存的值。

    • 示例:

    1. int main(){
    2. int a = 10;
    3. int * p; //定义一个指针p
    4. p=&a; //将变量a的地址赋给指针p
    5. //int * p = &a; //这个方式可以将定义和赋值写在一起
    6. *p=1000; //使用解引用影响指针p指向的内存区域,也就是变量a
    7. cout<<a<<endl;
    8. cout<<*p<<endl; //通过输出展示指针所产生的影响
    9. return 0;
    10. }

7.3 空指针

  • 用途:给指针变量初始化。
  • 特点:空指针指向的内存区域无权访问。
  • 定义一个空指针:int * p = NULL

7.4 野指针

  • 定义:指向一块未申请的内存区域。
  • 特点:
    • 指向的内存区域通常无法读取或修改。
    • 一旦尝试读取或修改,则会报错。

7.5 const修饰指针

  • 三种方式:

    • const修饰指针:常量指针
      • 用法:const int * p = NULL
      • 作用:指针所指向的内存地址中的值为常量,不可更改;但是指针所指的地址可以更改。
    • const修饰常量:指针常量
      • 用法:int * const p = NULL
      • 作用:指针本身为常量,指向的内存地址不可更改,但是其值可以更改。
    • const同时修饰指针和常量
      • 用法:const int * const p=NULL
      • 作用:指针所指的内存地址不可更改,值也不可更改。

7.6 指针和数组

用指针操作数组。

  • 示例:

    1. int main(){
    2. int arr[5]={1,2,3,4,5};
    3. int * p=arr; //将指针指向数组在内存中的首地址
    4. for (int i=0;i<5;i++){
    5. cout<<*p<<endl; //输出指针p指向的值
    6. p++; //每次循环将指针p向后偏移4字节,实现指针在数组中的遍历
    7. }
    8. return 0;
    9. }

7.7 指针和函数

利用指针作为函数的参数可以修改实参的值。

  • 示例:

    1. void swap(int *p1,int *p2){ //声明一个函数用来交换两个变量的值,形参为两个指针
    2. //这部分直接影响了指针所指向的内存空间,也就是main函数中a,b两个变量的值
    3. int temp=*p1;
    4. *p1=*p2;
    5. *p2=temp;
    6. }
    7. int main(){
    8. int a=1,b=2;
    9. cout<<"a="<<a<<" b="<<b<<endl;
    10. swap(&a,&b); //引用函数,实参为两个变量的地址
    11. cout<<"a="<<a<<" b="<<b<<endl;
    12. return 0;
    13. }

# 指针、函数、数组的搭配示例

利用冒泡循环对数组排序。

  1. int bubbleSort(int * arr,int len){ //使用函数封装冒泡循环的算法
  2. for (int i = 0;i<len;i++){
  3. for(int j=0;j<len-i-1;j++){
  4. if(arr[j]>arr[j+1]){
  5. int temp=arr[j];
  6. arr[j]=arr[j+1];
  7. arr[j+1]=temp;
  8. }
  9. }
  10. }
  11. }
  12. int main(){
  13. int arr []={1,5,3,2,7,8,3,5,2};
  14. int len=sizeof(arr)/sizeof (arr[0]); //获取数组的元素数量
  15. bubbleSort(arr,len); //调用函数,第一个实参为arr数组的内存地址
  16. for(int i = 0;i<len;i++){
  17. cout<<arr[i]<<endl;
  18. }
  19. }

8. 结构体

允许用户创建自定义数据类型,储存不同的数据类型。

8.1 定义和使用

  • 语法:struck 结构体名 { 结构体成员列表 };

  • 创建变量方式:

    • struck 结构体名 变量名 = {成员1;成员2;...}
    • struck 结构体名 变量名
    • 定义结构体时顺便创建变量
  • 使用变量的属性

    • 格式:变量名.属性
  • 示例:

    1. struct student //定义结构体(全局),struct关键字不可省略
    2. {
    3. string name;
    4. int age;
    5. int score;
    6. }s3; //此处s3为使用方法三创建的结构体变量
    7. int main(){
    8. //方法一
    9. struct student s1={"Maicss",19 , 650 } ; //struct关键字可以省略
    10. cout<<"name:"<<s1.name<<"\tage:"<<s1.age<<"\tscore:"<<s1.score<<endl;
    11. //方法二
    12. struct student s2; //struct关键字可以省略
    13. s2.name="qian";
    14. s2.age=18;
    15. s2.score=600;
    16. cout<<"name:"<<s2.name<<"\tage:"<<s2.age<<"\tscore:"<<s2.score<<endl;
    17. //方法三
    18. //创建结构体变量在定义结构体之后
    19. s3.name="son";
    20. s3.age=1;
    21. s3.score=700;
    22. cout<<"name:"<<s3.name<<"\tage:"<<s3.age<<"\tscore:"<<s3.score<<endl;
    23. return 0;
    24. }

8.2 结构体数组

  • 作用:将自定义的结构体放入数组中方便维护。

  • 语法:struck 结构体名 数组名[成员数] = { { } , { } , { } }

  • 示例:

    1. struct student { //定义一个结构体
    2. string name;
    3. int age;
    4. int score;
    5. };
    6. int main()
    7. {
    8. //创建一个结构体数组
    9. struct student stuarr[3]{
    10. {"Maicss",19,100},
    11. {"Max",20,99},
    12. {"Pig",10,30}
    13. };
    14. //给结构体数组中第三个成员的score修改为98
    15. stuarr[2].score =98;
    16. //遍历输出结构体数组所有成员属性
    17. for (int i=0 ;i<3;i++){
    18. cout<<"name:"<<stuarr[i].name<<"\tage:"<<stuarr[i].age<<"\tscore:"<<stuarr[i].score<<endl;
    19. }
    20. return 0;
    21. }

8.3 结构体指针

  • 作用:通过指针访问结构体中的成员。

  • 利用操作符->可以通过结构体指针访问结构体属性。

  • 示例:

    1. struct student { //定义一个结构体
    2. string name;
    3. int age;
    4. int score;
    5. };
    6. int main()
    7. {
    8. //创建一个结构体数组
    9. student s{"Maicss",19,100};
    10. //创建一个结构体指针
    11. student * p = &s;
    12. //通过指针读取结构体变量属性
    13. cout<<"name:"<<p->name<<"\tage:"<<p->age<<"\tscore:"<<p->score<<endl;
    14. return 0;
    15. }

8.4 结构体嵌套

  • 作用:让一个结构体成为另一个结构体的成员。

  • 示例:

    1. struct student { //定义一个结构体
    2. string name;
    3. int age;
    4. int score;
    5. };
    6. struct teacher { //定义另一个结构体
    7. string name;
    8. int id;
    9. int age;
    10. student std;
    11. };
    12. int main()
    13. {
    14. //创建一个结构体变量
    15. student std1 {"max",18,100};
    16. //创建另一个结构体变量
    17. teacher thr1 {"maicss",197835,23,std1};
    18. //创建一个结构体指针
    19. teacher * p = &thr1;
    20. //修改老师maicss的学生max的年龄为19
    21. thr1.std.age=19;
    22. //通过指针读取结构体变量属性
    23. cout<<"name:"<<p->name
    24. <<"\tID:"<<p->id
    25. <<"\tage:"<<p->age
    26. <<"\tstudent:"<<p->stds.name //读取嵌套在teacher结构体中的student的属性
    27. <<"\tstudentAge:"<<p->stds.age //查看更改后的学生年龄
    28. <<endl;
    29. return 0;
    30. }

8.5 将结构体作为函数参数传递

  • 值传递

    • 直接在函数参数中传递结构体变量。
    • 特点:在函数内对参数值(结构体属性)的修改不会影响实参。
  • 指针传递

    • 在函数参数中传递结构体指针。
    • 特点:对值(结构体属性)的修改会影响到实参。
  • const 保护

    • 用const修饰指针,防止在函数中无意影响到函数实参。
  • 示例:

    1. struct student { //定义一个结构体
    2. string name;
    3. int age;
    4. int score;
    5. };
    6. void print1(student a){ //结构体在函数参数中通过值传递
    7. cout<<"name:"<<a.name<<" age:"<<a.age<<" score:"<<a.score<<endl;
    8. }
    9. void print2(student * p){ //结构体在函数参数中通过指针传递
    10. p->age=100; //通过指针修改结构体属性的值可以影响到实参
    11. cout<<"name:"<<p->name<<" age:"<<p->age<<" score:"<<p->score<<endl;
    12. }
    13. int main()
    14. {
    15. //创建一个结构体变量
    16. student std1 {"max",18,100};
    17. //创建一个结构体指针
    18. student * p =&std1;
    19. //调用函数进行输出
    20. print1(std1); //值传递
    21. print2(&std1);//指针传递
    22. cout<<"name:"<<std1.name<<" age:"<<std1.age<<" score:"<<std1.score<<endl; //验证print2函数的修改
    23. return 0;
    24. }

9. 联系人管理系统实例

一个完整的联系人管理系统

10 程序的内存模型

10.1 内存分区模型

  • 代码区:存放函数体的二进制代码,由操作系统进行管理。
  • 全局区:存放全局变量和静态变量以及常量。
  • 栈区:由编译器自动分配和释放,存放函数的参数值,局部变量等。
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由系统回收。

10.2 分区意义

  • 不同区域存放的数据赋予不同的生命周期,更强大的灵活的编程。

10.3 程序运行前

程序编译后,生成exe可执行程序,未执行该程序前分为两个区域。

  • 代码区:
    • 存放CPU执行的机器指令。
    • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
  • 全局区:
    • 全局变量和静态变量存放在此。
    • 全局区也包含了常量区,字符串常量和其他非局部常量也存放在此。
    • 该区域的数据在程序结束后由操作系统释放。

10.4 程序运行后

  • 栈区

    • 由编译器自动分配和释放,存放函数的参数值,局部变量等。

    • 注意:不要返回栈区的地址,栈区开辟的数据由编译器自动释放。

    • 栈区的数据在函数执行完后自动释放。

    • 示例:

      1. int* integer(){
      2. int a=1;
      3. return &a; //返回局部变量a的内存地址
      4. }
      5. int main()
      6. {
      7. int * p=integer(); //将指针p指向局部变量a的地址
      8. cout <<*p<< endl; //第一次解引用p
      9. //这里之所以会输出a是因为编译器为程序保留了一次内存
      10. cout <<*p<< endl;//第二次解引用p
      11. //编译器不再为程序保留,所以不再是原来的数值
      12. return 0;
      13. }
  • 堆区

    • 由程序员分配和释放,若程序员不释放,程序结束时由系统回收。

    • 使用new在堆区开辟内存。

      • 语法:new 数据类型
      • new创建的数据会返回该数据类型对应的指针。
    • 使用delect释放内存。

      • 语法:delect 指针
      • 释放后不能继续访问。
  • 为了防止出现野指针,在堆区内存空间被释放时要将指向该内存的指针指向NULL

    • 示例:
      1. int main()
      2. {
      3. int * p = new int(10); //使用new开辟一块内存储存整数10并将地址给指针p
      4. int * p2 = new int[10]; //创建一个数组
      5. cout<<*p<<endl;//解引用指针p结果为10
      6. //赋值部分省略
      7. delete p;//释放内存
      8. delete[] p2;//释放数组
      9. }

11. C++中的引用

引用的本质是指针常量。

11.1 引用的基本使用

  • 作用:给变量起别名。

  • 语法:&别名=原名

  • 注意事项:

    • 引用必须要初始化。
    • 引用一旦初始化就不可以更改。

11.2 引用作函数参数

  • 作用:就像指针一样,可以影响到实参。

  • 示例:

    1. //创建一个交换函数
    2. void swap(int &a,int &b){ //使用引用传递参数
    3. int temp;
    4. temp=a;
    5. a=b;
    6. b=temp;
    7. }
    8. int main()
    9. {
    10. int a,b;
    11. a=0;b=1;
    12. swap(a,b);
    13. cout<<a<<"\n"<<b<<endl;
    14. }

    11.2 引用作函数的返回值

    • 作用:可以让函数的调用作为左值。

    • 示例:

      1. //创建一个函数
      2. int& num(){
      3. static int a=10; //创建静态变量,储存在全局区中
      4. return a;
      5. }
      6. int main()
      7. {
      8. int& ref=num(); // 给num函数中的a搞一个引用
      9. num()=1000;
      10. cout<<ref<<endl; //验证将函数调用作为左值是否有效
      11. return 0;
      12. }

11.3 常量引用

  • 作用:可以用来修饰形参,防止实参被影响。

  • 示例:

    1. void change(const int& a){ //使用const修饰形参
    2. //此处不可修改a的值
    3. cout<<a;
    4. }
    5. int main()
    6. {
    7. int a=1;
    8. change(a);
    9. return 0;
    10. }

12. 类和对象

C++面向对象的三大特性:封装、继承、多态

万物皆对象、对象上有其属性和行为。

12.1 封装

12.1.1 封装的意义

  • 封装是c++面向对象三大特征之一

  • 封装的意义:

    • 将属性和行为作为一个整体来表现实物。
    • 将属性和行为加以权限控制。
  • 示例:

    1. #include <iostream>
    2. using namespace std;
    3. #define Pi 3.1415926 //定义一个宏常量Pi
    4. class circle{ //创建一个circle类
    5. public: //定义公有部分
    6. int r; //定义一个半径属性r
    7. double circle_C(){ //定义一个成员函数,计算周长
    8. return 2*r*Pi;
    9. }
    10. void set_r(double set_r){
    11. r=set_r;
    12. }
    13. };
    14. int main()
    15. {
    16. circle yuan; //通过circle类实例化一个对象
    17. yuan.r=10; //给对象的公有属性r赋值
    18. cout << yuan.circle_C() << endl; //通过成员函数输出周长
    19. yuan.set_r(5);
    20. cout << yuan.circle_C() << endl; //通过成员函数输出周长验证修改
    21. return 0;
    22. }

12.1.2 访问权限控制

  • 总共有三个权限

    名称 意义 特点
    public 公共权限 类内和类外都可以访问
    protected 保护权限 类内可以访问,类外不可以访问,子可以访问父的保护内容
    private 私有权限 类内可以访问,类外不可以访问,子不可访问父的私有内容
  • classstruct的区别:

    • class默认是私有权限,struct默认是公有权限。

12.1.3 成员属性私有化

  • 优点:

    • 对成员属性私有化可以自己控制读写权限。
    • 对于读写权限,可以检测数据的有效性。
  • 示例:

    1. #include <iostream>
    2. using namespace std;
    3. class human{
    4. public:
    5. void setName(string set_name){ //定义一个用来设置姓名的成员函数
    6. name=set_name;
    7. }
    8. string getName(){ //定义一个获取姓名的成员函数
    9. return name;
    10. }
    11. void setAge(int set_age){ //定义一个用来设置年龄的成员函数
    12. if (set_age<0 || set_age>150){ //使用if语句判断所给值是否合法
    13. cout<<"非法数据!";
    14. }else{
    15. age=set_age;
    16. }
    17. }
    18. int getAge(){ //定义一个用来获取年龄的成员函数
    19. return age;
    20. }
    21. void setLover(string set_lover){ //定义一个用来设置爱人的成员函数
    22. lover=set_lover;
    23. }
    24. private: //私有属性的定义
    25. string name;
    26. int age;
    27. string lover;
    28. };
    29. int main()
    30. {
    31. human maicss; //实例化一个对象
    32. //使用成员函数设置相关属性
    33. maicss.setName("Maicss");
    34. maicss.setAge(18);
    35. maicss.setLover("qian");
    36. //使用成员函数获取相关私有函数的值在
    37. cout<<"name:"<<maicss.getName()<<" age:"<<maicss.getAge()<<endl;
    38. return 0;
    39. }

12.2 对象的初始化和清理

12.2.1 构造函数和析构函数

  • 对象的初始化清理是两个重要的问题。

    • 一个对象或者变量没有初始化状态,其使用后果是未知的。
    • 使用完一个对象或者变量,没有及时的清理,也会造成一定的安全问题。
  • 注意:

    • 构造函数和析构函数可以解决上述问题,由编译器自动调用,完成对象的初始化和清理工作。对象的初始化和清理是必须要做的事情。因此不必要提供构造和析构函数,编辑器会提供,但是编译器提供的构造函数和析构函数是空实现
    • 一个空对象所占用的内存为1字节,因为对象必须要有一个首地址。
  • 作用:

    • 构造函数:主要用在创建对象时给对象的成员属性赋值,由编译器自动调用。
    • 析构函数:主要用于对象销毁前的自动调用,负责清理工作。
  • 语法:

    • 构造函数:类名(){}

      1. 构造函数,没有返回值,也不用写void。
      2. 函数名称和类名相同。
      3. 构造函数可以有参数,因此可以发生重载。
      4. 程序在调用对象的时候自动调用构造函数,并且只会调用一次,无需手动调用。
    • 析构函数:~类名(){}

      1. 析构函数,没有返回值,也不用写void。
      2. 函数名称和类名相同,在名称前加上~
      3. 析构函数不可以有参数,因此无法发生重载。
      4. 程序在对象销毁前自动调用析构函数,并且只会调用一次,无需手动调用。
    1. 手动释放在堆区申请的空间要用delete语句。
  • 示例:

    1. class human{
    2. public: //为了保证构造函数和析构函数能被全局调用,所以需要设置为public权限
    3. human(){ //创建一个构造函数
    4. cout<<"human的构造函数被调用\n";
    5. }
    6. ~human(){ //创建一个析构函数
    7. cout<<"human的析构函数被调用\n";
    8. }
    9. };
    10. void hum(){ //函数中实例化一个对象,函数运行结束后对象被销毁
    11. human m;
    12. }
    13. int main()
    14. {
    15. human maicss;//创建对象同时运行构造函数
    16. hum(); //调用hum函数来创建对象,同时运行构造函数
    17. //hum函数运行结束时对象m被销毁,同时运行析构函数
    18. return 0;//mian函数返回0,程序结束前会运行析构函数
    19. }

12.2.2 构造函数的分类以及调用

  • 两种分类方式:

    • 按参数分为:有参构造和无参构造
    • 按类型分为:普通构造和拷贝构造
      • 拷贝构造函数是用来将一个对象的所有属性拷贝到新对象中。
  • 三种调用方式:

    • 括号法
    • 显示法
    • 隐式转换法
  • 注意事项:

    • 使用默认构造函数时,不要用(),否则会被认为是函数定义。
    • 不要利用拷贝构造函数初始化匿名对象,编译器会认为是参数对象的声明。
  • 示例(有点长):

    1. class human{
    2. public:
    3. human(){
    4. cout<<"human的无参(默认)构造函数被调用\n";
    5. }
    6. human(int a){
    7. age=a;
    8. cout<<"human的有参构造函数被调用\n";
    9. }
    10. human(const human &p){ //const的意义是不允许在构造函数中修改传入对象的属性,只读。
    11. age=p.age;
    12. cout<<"human的拷贝构造函数被调用\n";
    13. }
    14. ~human(){
    15. cout<<"human的析构函数被调用\n";
    16. }
    17. int age;
    18. };
    19. void hum1(){ //括号法调用
    20. human m1; //无参构造
    21. human m2(10); //有参构造
    22. human m3(m1); //拷贝构造
    23. }
    24. void hum2(){ //显示法
    25. human m1; //无参构造
    26. human m2=human(10); //有参构造
    27. human m3=human(m1); //拷贝构造
    28. }
    29. void hum3(){ //隐式转换法
    30. human m1; //无参构造
    31. human m2=10; //有参构造
    32. human m3=m2; //拷贝构造
    33. }
    34. int main()
    35. {
    36. hum1();
    37. hum2();
    38. hum3();
    39. return 0;
    40. }

12.2.3 拷贝构造函数的调用时机

C++中拷贝构造函数的调用时机通常由三种情况:

  • 使用一个创建完毕的对象来初始化一个新的对象。
  • 以值传递的方式给函数的参数传值
  • 以值方式返回局部对象

12.2.4 构造函数的调用规则

默认情况下,C++编译器至少给一个类添加三个函数:

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对其属性值进行拷贝。

构造函数调用规则:

  • 如果用户自定义有构造函数,编译器不再提供默认无参构造函数,但是会提供默认拷贝构造。
  • 如果用户定义拷贝构造函数,编译器不再提供其他构造函数。
  • 总结:写了有参必须写无参,写了拷贝就得有参无参都写上。

12.2.5 深拷贝与浅拷贝

  • 定义:

    • 浅拷贝:简单的赋值操作。如果是用编译器提供的拷贝函数会利用浅拷贝。
    • 深拷贝:在堆区重新申请一块内存空间,进行拷贝操作。
  • 注意:

    • 浅拷贝可能导致堆区内存重复释放。
  • 示例(浅拷贝):

    1. class person{
    2. public:
    3. person(){
    4. cout<<"无参构造函数被调用\n";
    5. }
    6. person(int age,int height){
    7. m_height=new int (height); //在堆区申请一块内存区域用来存放height
    8. m_age=age;
    9. cout<<"有参构造函数被调用\n";
    10. }
    11. ~person(){
    12. if(m_height!=NULL){
    13. delete m_height; //释放m_height指向的内存区域
    14. //析构函数会被执行两次,因为两个对象在man()函数结束后被销毁,但是由于浅拷贝将指针拷贝给第二个对象,因此两个对象的m_height指针指向了堆区的同一块内存区域,这块内存区域释放两次,会报错。
    15. m_height=NULL; //将指针指向NULL,防止野指针的出现。
    16. }
    17. cout<<"析构函数被调用\n";
    18. }
    19. private:
    20. int m_age;
    21. int * m_height; //创建一个int指针指向有参构造申请的内存区域
    22. };
    23. void man(){
    24. person one(16,160);
    25. person two(one);//浅拷贝
    26. }
    27. int main()
    28. {
    29. man();
    30. cout << "Hello World!" << endl;
    31. return 0;
    32. }
  • 示例(深拷贝):

    1. class person{
    2. public:
    3. person(){
    4. cout<<"无参构造函数被调用\n";
    5. }
    6. person(int age,int height){
    7. m_height=new int (height); //在堆区申请一块内存区域用来存放height
    8. m_age=age;
    9. cout<<"有参构造函数被调用\n";
    10. }
    11. person(const person &p){
    12. m_age=p.m_age;
    13. m_height=new int (*p.m_height);//在堆区重新申请一块内存实现深拷贝
    14. }
    15. ~person(){
    16. if(m_height!=NULL){
    17. delete m_height; //释放m_height指向的内存,此时不会出现多次释放同一内存空间的问题
    18. m_height=NULL; //将指针指向NULL,防止野指针的出现。
    19. }
    20. cout<<"析构函数被调用\n";
    21. }
    22. int m_age;
    23. int * m_height; //创建一个int指针指向有参构造申请的内存区域
    24. };
    25. void man(){
    26. person one(16,160);
    27. person two(one);//由于定义了拷贝函数,所以此处会通过定义实现深拷贝
    28. cout<<one.m_age<<" "<<*one.m_height<<endl;
    29. cout<<two.m_age<<" "<<*two.m_height<<endl;
    30. }
    31. int main()
    32. {
    33. man();
    34. cout << "Hello World!" << endl;
    35. return 0;
    36. }

12.2.6 初始化列表

  • 作用:初始化列表语法可以用来初始化对象属性。

  • 语法:构造函数():属性1(值1),属性2(值2), ...

  • 示例:

    1. class person{
    2. public:
    3. person(int a,int b):age(a),height(b){}
    4. int age;
    5. int height;
    6. };
    7. void man(){
    8. person one(18,180);
    9. cout<<"age:"<<one.age<<" height:"<<one.height<<endl;
    10. }
    11. int main()
    12. {
    13. man();
    14. return 0;
    15. }

12.2.7 类对象作为类成员

  • 定义:一个类声明的对象成为另一个类的属性成员。

  • 例如:

    1. class A{}
    2. class B{
    3. A a;
    4. }
  • 注意:

    • 构造时先构造作为属性成员的对象(A)再构造对象本身(B)。
    • 析构时先析构对象本身(B)再析构各个属性成员(A)。

12.2.8 静态成员

静态成员可以看作属于类的作用域,被所有对象公用。

静态成员变量和静态成员函数都有权限控制。

  • 静态成员变量

    • 作用:所有成员公用一个成员变量。

    • 语法:static 数据类型 变量名

    • 注意:

      • 静态成员变量要在类内声明,类外初始化。
      • 在编译阶段会分配内存
    • 示例:

      1. class human{
      2. public:
      3. static int age;//在类内的声明
      4. };
      5. int human::age=100;//在类外的初始化
      6. int main()
      7. {
      8. human a;
      9. cout<<a.age<<endl;
      10. human b;
      11. b.age=18;//使用对象b给静态变量重新赋值
      12. //也可以通过类名操作成员变量
      13. human::age=18;
      14. cout<<a.age<<endl;//此时对象a的age值也会随b变成18
      15. return 0;
      16. }
  • 静态成员函数

    • 作用:所有成员公用一个成员函数。属于类的作用域。

    • 语法:static 函数返回值类型 函数名();

    • 注意:静态成员函数属于类的作用域,只能操作静态成员变量。

    • 示例:

      1. class human{
      2. public:
      3. static void func(){
      4. age=1;
      5. }
      6. static int age;//在类内的声明
      7. };
      8. int human::age=100;//在类外的初始化
      9. int main()
      10. {
      11. human a;
      12. cout<<a.age<<endl;
      13. human b;
      14. //访问静态成员函数,下面两种方式效果完全相同
      15. b.func();//通过对象访问
      16. human::func();//通过类的作用域访问
      17. cout<<a.age<<endl;
      18. return 0;
      19. }

12.3 C++对象模型和this指针

12.3.1 成员变量和成员函数分开储存

  • 非静态成员变量属于类的对象
  • 静态成员变量不属于类的对象。
  • 非静态成员函数不属于类的对象。
  • 静态成员函数不属于类的对象。

12.3.2 this指针概念

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

  • 特点:

    • 隐含在每一个非静态成员函数内的一种特殊指针。
    • this指针不需要定义,直接用即可。
  • 使用场景:

    • 当形参名和成员变量名相同时,可以用this指针区分。
    • 在类的非静态成员函数中返回对象本身,可以用return *this
  • 示例:

    1. class human{
    2. public:
    3. void c_age(int age){
    4. this->age=age;//用this指针表示成员变量
    5. }
    6. human& addage(human &p){ //函数返回值要用引用的方式返回,否则会创建新对象
    7. this->age+=p.age;
    8. return *this;
    9. }
    10. int age;
    11. };
    12. void func(){
    13. human maicss;
    14. human p1;
    15. p1.age=10;
    16. maicss.c_age(18);
    17. maicss.addage(p1).addage(p1).addage(p1); //链式编程思想
    18. cout<<"maicss的年龄是:"<<maicss.age<<endl;
    19. }
    20. int main()
    21. {
    22. func();
    23. return 0;
    24. }

12.3.3 空指针调用成员函数

空指针可以调用成员,但是为了防止崩溃,要避免访问成员变量。

  1. class human{
  2. public:
  3. void printname(){
  4. cout<<"name is maicss\n";
  5. }
  6. void printage(){
  7. if (this==NULL){//防止程序崩溃进行的保险措施
  8. return ;
  9. }
  10. cout<<"age is "<<this->age<<endl;
  11. }
  12. int age;
  13. };
  14. void func(){
  15. human * maicss=NULL;
  16. maicss->printname();//使用空指针访问成员函数
  17. maicss->printage();//使用空指针在成员函数中访问成员变量
  18. }
  19. int main()
  20. {
  21. func();
  22. return 0;
  23. }

12.3.4 const修饰成员函数

常函数

  • 成员函数后加const,我们称这个函数为常函数。
  • 常函数内不可以修改成员属性。
  • 成员属性加关键字mutable后,在常函数中依然可以修改。

常对象

  • 声明对象前加const称该对象为常对象。
  • 常对象只能调用常函数。

示例

  1. class human{
  2. public:
  3. human(){}//新建一个无参构造函数,为了创建常对象
  4. void func() const{
  5. //age=18; //由于成员函数末尾加了const,所以函数体内不允许修改成员变量
  6. height=180; //由于成员变量前加了mutable关键字,所以该变量可以在函数中修改
  7. }
  8. void func2(){}
  9. int age;
  10. mutable int height;
  11. };
  12. int main()
  13. {
  14. human maicss;
  15. const human maicss2;
  16. //maicss2.func2(); //由于是常对象,所以只能调用常函数。也只能修改带有mutable关键字的成员变量
  17. maicss.func();
  18. return 0;
  19. }

12.4 友元

  • 作用:让一个函数或类,访问另一个类中的私有成员。

  • 关键字:friend

  • 三种实现方式:

    • 全局函数作友元
    • 类作友元
    • 成员函数作友元
  • 示例:

    1. class room; //声明类
    2. class goodgay2{
    3. public:
    4. goodgay2();
    5. void visit();
    6. room * m_room; //创建一个指针
    7. };
    8. class goodgay{
    9. public:
    10. goodgay(); //声明构造函数
    11. void visit(); //声明成员函数用于访问room的私有成员
    12. room * m_room; //创建一个指针
    13. };
    14. class room{
    15. friend void text(); //将全局函数作为友元
    16. friend class goodgay; //将另一个类作为友元
    17. friend void goodgay2::visit();
    18. public:
    19. string sittingroom="客厅";
    20. private:
    21. string bedroom="卧室";
    22. };
    23. goodgay::goodgay(){ //类外定义构造函数
    24. m_room=new room; //在构造函数中于堆区创建一个对象
    25. }
    26. goodgay2::goodgay2(){
    27. m_room=new room;
    28. }
    29. void goodgay::visit(){ //类外定义成员函数
    30. cout<<"b在访问:"<<m_room->bedroom<<endl;
    31. cout<<"b在访问:"<<m_room->sittingroom<<endl;//访问room的私有成员
    32. };
    33. void goodgay2::visit(){
    34. cout<<"c在访问:"<<m_room->bedroom<<endl;//访问room的私有成员
    35. }
    36. void text(){
    37. room a;
    38. cout<<"a访问了:"<<a.bedroom<<endl;
    39. goodgay b;
    40. b.visit(); //通过访问visit成员函数访问room的私有成员
    41. goodgay2 c;
    42. c.visit();
    43. }
    44. int main()
    45. {
    46. text();
    47. return 0;
    48. }

12.5 运算符的重载

12.5.1 加号运算符的重载

  • 方式:

    • 使用成员函数重载
    • 使用全局函数重载
  • 示例:

    1. //使用成员函数重载
    2. class Person{
    3. public:
    4. int age;
    5. int height;
    6. public:
    7. Person operator+(Person &p); //使用成员函数对加号的重载
    8. };
    9. Person Person::operator+(Person &p){//定义重载函数
    10. Person temp;
    11. temp.age=this->age+p.age;
    12. temp.height=this->height+p.height;
    13. return temp;
    14. }
    15. int main(){
    16. Person p1;
    17. Person p2;
    18. p1.age=10;
    19. p2.age=18;
    20. p1.height=159;
    21. p2.height=180;
    22. Person p3=p1+p2;
    23. cout<<"P3的年龄为:"<<p3.age<<" P3的身高为:"<<p3.height<<endl;
    24. return 0;
    25. }
    1. //使用全局函数重载
    2. class Person{
    3. public:
    4. int age;
    5. int height;
    6. };
    7. Person operator+(Person &p1,Person &p2){ //使用成员函数对加号的重载
    8. Person temp;
    9. temp.age=p1.age+p2.age;
    10. temp.height=p1.height+p2.height;
    11. return temp;
    12. }
    13. int main(){
    14. Person p1;
    15. Person p2;
    16. p1.age=10;
    17. p2.age=18;
    18. p1.height=159;
    19. p2.height=180;
    20. Person p3=p1+p2;
    21. cout<<"P3的年龄为:"<<p3.age<<" P3的身高为:"<<p3.height<<endl;
    22. return 0;
    23. }

12.6 继承

继承使面向对象三大特性之一

定义某些了类时,下一级别的成员拥有上一级别的共性,还有自己的特性。

使用继承可以尽量减少代码

  • 用法:class A : public B;

  • 说明:上述A为子类(派生类),B为父类(基类)。

  • 示例:

    1. class base{ //创建一个基类
    2. public:
    3. int age;
    4. string name;
    5. };
    6. class human : public base{ //创建一个以base为基类的派生类
    7. public:
    8. int score;
    9. };
    10. class dog : public base{//另一个以base为基类的派生类
    11. public:
    12. human master;
    13. };
    14. void test1(){ //对派生类中属性的访问示例
    15. human maicss;
    16. dog dazhuang;
    17. dazhuang.age=4;
    18. maicss.age=20;
    19. dazhuang.name="DAZ";
    20. maicss.name="Maicss";
    21. dazhuang.master=maicss;
    22. maicss.score=100;
    23. }
    24. int main()
    25. {
    26. void test1();
    27. return 0;
    28. }
  • 注意:

    • 经过测试,若定义基类时关键字改为private,那么所继承的所有属性全为私有属性;若为public,则正常继承。(这是依我自己理解的)
    • 被定义为protected的成员变量为保护权限,此时子类可以访问这种成员变量,但是如果是private则无法访问,这也是两者的唯一区别。

13. 文件操作

使用文件操作需要包含头文件<fstream>

文件类型分为两种:

  1. 以ASCII码形式储存的文本数据。
  2. 二进制文件形式储存的,用户一般读不懂。

操作文件三大类:

  1. ofstream 写文件
  2. ifstream 读文件
  3. fstream 读写文件

13.1文本文件

13.1.1写文件的基本操作

写文件的基本步骤:

  1. //1.包含头文件
  2. #include <fstream>
  3. //2.创建流对象
  4. ofstream ofs;
  5. //3.打开文件
  6. ofs.open("文件路径",打开方式);
  7. //4.写数据
  8. ofs<<"文本文件";
  9. //5.关闭文件
  10. ofs.close();
打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在,先删除再创建
ios::binary 二进制方式

文件打开方式配合使用需要|符号

示例:

  1. #include <iostream>
  2. #include <fstream> //包含文件流头文件
  3. using namespace std;
  4. int main()
  5. {
  6. std::ofstream ofs; //创建一个ofstream类对象,实现写文件
  7. ofs.open("text.txt",ios::out); //打开文件
  8. ofs<<"你好世界"; //写到文件
  9. return 0;
  10. }

13.1.2读文件的基本操作

写文件的基本步骤:

  1. //包含头文件
  2. #include <fstream>
  3. //创建流对象
  4. std::ifstream ifs;
  5. //打开文件
  6. ifs.open("文件路径",打开方式);
  7. if (!ifs.is_open()){
  8. cout<<"文件打开失败"<<endl;
  9. return ;
  10. }
  11. //读入文件
  12. //第一种方式
  13. char text1[1024]={0};
  14. while (ifs>>text1) {
  15. cout<<text1<<endl;
  16. }
  17. //第二种方式
  18. char text2[1024]={0};
  19. while (ifs.getline(text2,sizeof(text2))){
  20. cout<<text2<<endl;
  21. }
  22. //第三种方式
  23. string text3;
  24. while (getline(ifs,text3)) {
  25. cout<<text3<<endl;
  26. }
  27. //第四种方式
  28. char text4;
  29. while ((text4=ifs.get())!=EOF){
  30. cout<<text4;
  31. }
  32. //关闭文件
  33. close

14. C++中的STL

STL是为了提高软件代码的复用性而产生的一种标准模板库

14.1 STL的基本概念

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

14.2 vector容器的基本使用

  • 容器:vector

  • 算法:for_each

  • 迭代器:vector<int>::iterator

  • 示例:

    1. #include <iostream>
    2. using namespace std;
    3. #include <vector>//使用容器必须引入头文件
    4. void print(int i) {//第三种遍历方法要用到
    5. cout << i << endl;
    6. }
    7. int main() {
    8. vector<int> v;
    9. v.push_back(1);//使用尾插添加元素
    10. v.push_back(2);
    11. v.push_back(3);
    12. v.push_back(4);
    13. //遍历容器的第一种方法
    14. vector<int>::iterator head = v.begin();//begin()会返回指向容器中第一个元素的指针
    15. vector<int>::iterator tail = v.end();//end()会返回指向容器中最后一个元素的下一个位置的指针
    16. while (head != tail)
    17. {
    18. cout << *head << endl;
    19. head++;
    20. }
    21. //遍历容器的第二种方法(是第一种方式的简化)
    22. for (vector<int>::iterator h = v.begin(); h != v.end(); h++) {
    23. cout << *h << endl;
    24. }
    25. //遍历容器的第三种方法(使用标准算法库中的for_each)
    26. for_each(v.begin(),v.end(),print);
    27. getchar();
    28. return 0;
    29. }

14.3 string容器的基操

  • stringchar *的区别
    • 本质上两者区别不大,前者是后者的封装,可以管理字符串。
  • 构造函数
    • string(); 无参构造,创建一个空的字符串。
    • string(const char* s); 使用字符串s进行初始化。
    • string(const string& str);使用字符串str初始化。
    • string(int n,char c);使用n个字符c初始化。

# 其他内容

随机数生成

  • rand()函数
    • 用法:rand()%10可以生成0~9的随机数。
    • 置随机数种子:srand((unsigned int)time(NULL))(需要#include <ctime>)

内存

  • 获取内存地址

    • 数组的首地址可以直接使用数组的名字。
    • 或者使用取址符“&”。
  • 注意:

    • 0~255之间的内存是无法访问的。

静态变量

  • 在普通变量前加static为静态变量。
  • 静态变量储存在内存的全局区中。

原文链接:http://www.cnblogs.com/maicss/p/14616887.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号