• Coursera课程笔记----C++程序设计----Week1&2


    从C走进C++(Week1&2)

    函数指针

    基本概念

    • 程序运行期间,每个函数都会占用一段连续的内存空间
    • 函数名就是该函数所占内存区域的起始地址(入口地址)
    • 可以将函数的入口地址赋给指针变量,使该指针变量指向该函数,通过指针变量就可以调用这个函数
    • 这种指向函数的指针变量被称为“函数指针

    定义形式

    • 类型名(* 指针变量名)(参数类型1,参数类型2,......)
    int (*pf)(int , char);
    //pf为一个函数指针,它所指向的函数的返回值是int,2个参数一个是int类型一个是char类型
    

    使用方法

    • 可以用一个原型匹配的函数的名字给一个函数指针赋值
    • 通过函数指针调用他所指向函数
      • 函数指针名 (实参表)
    #include <stdio.h>
    void PrintMin(int a, int b)
    {
      if(a < b)
        printf("%d",a);
      else
        printf("%d",b);
    }
    int main()
    {
      void(* pf)(int, int);
      int x = 4, y = 5;
      pf = PrintMin;
      pf(x,y);
      return 0;
    }
    //pf指针指向PrintMin
    

    qsort库函数

    • 对数组排序,需要知道
      1. 数组起始地址
      2. 数组元素个数
      3. 每个元素的大小(从而得出每个元素的地址)
      4. 元素谁前谁后的规则
    void qsort(void *base, int nelem, unsigned int width, int(* pfCompare)(const void*,const void*));
    //base:待排序数组的起始地址
    //nelem:待排序数组的元素个数
    //width:待排序数组的每个元素的大小(以字节为单位)
    //pfCompare:比较函数的地址
    //pfCompare:函数指针,它指向一个“比较函数”,该比较函数的形式如下
    //int 函数名 (const void * elem1,const void * elem2);
    //比较函数是程序员自己编写的
    
    • 排序就是一个不断比较并交换位置的过程
    • qsort函数在执行期间,会通过pfCompare指针调用“比较函数”,调用时将要比较的两个元素的地址传给“比较函数”,然后根据“比较函数”返回值判断哪个应该排在前面
    • 比较函数编写规则
      • 如果*elem1应该在前,函数返回负整数
      • 如果*elem2应该在前,函数返回正整数
      • 如果无所谓前后,函数返回0
    • 实例
      • 功能:调用qsort库函数,将一个unsigned int数组按照个位数从小到大进行排序
    #include <stdio.h>
    #include <stdlib.h>
    
    int MyCompare(const void * elem1,const void * elem2)
    {
      unsigned int * p1, * p2;
      p1 = (unsigned int *) elem1; //"*elem1" 非法,编译器不知道void指针指向的元素有多少个字节
      p2 = (unsigned int *) elem2; //"*elem2" 同上
      return (*p1 % 10) - (*p2 % 10);
    }
    #define NUM 5
    int main()
    {
      unsigned int an[NUM] = (8,123,11,10,4);
      qsort(an,NUM,sizeof(unsigned int),MyCompare);
      for(int i=0;i<NUM;i++)
        printf("%d",an[i]);
      return 0;
    }
    

    命令行参数

    以命令行方式运行程序

    • notepad sample.txt
      • notepad程序如何得知,用户在以命令行方式运行它的时候,后面跟着什么参数?

    命令行参数

    • 用户在CMD窗口输入可执行文件名的方式启动程序时,跟在可执行文件名后面的那些字符串,称为“命令行参数”。
    • 命令行参数可以有多个,用空格分隔
    • 举例
      • copy file1.txt file2.txt
      • "copy","file1.txt","file2.txt"就是命令行参数
    • 如何获得命令行参数
      • argc (argument counter):代表启动程序时,命令行参数的个数。C/C++语言规定,可执行程序程序本身的文件名,也算一个命令行参数,因此,argc的值至少是1
      • argv (argument vector):指针数组,其中的每个元素都是一个char* 类型的指针,该指针指向一个字符串,这个字符串里就存放着命令行参数
      • 提示: argument是实参,parameter是形参
      • 由于命令行参数之间用空格分隔,如果其本身就含有空格,则可用双引号括起来
    int main(int argc, char * argv[])
    {
      ...
    }
    

    位运算

    基本概念

    • 位运算:
      • 用于对整数类型(int, char, long...)变量中的某一位(bit)或者若干位进行操作。比如:
        1. 判断某一位是否为1
        2. 只改变其中某一位,而保持其他位都不变
      • C/C++语言提供了六种位运算符来进行位运算操作:
        • & 按位与(双目)
        • |按位或(双目)
        • ^ 按位异或 (双目)
        • ~ 按位非(取反)(单目)
        • <<左移(双目)
        • >>右移(双目)

    按位与“&”

    • 将参与运算的两数,各对应的二进制位进行操作,只有对应的两个二进制位均为1时,结果对应的二进制位才为1,否则为0

    • 通常用来将某变量中的某些位清0切同时保留其他位不变,也可用来获取某变量中的某一位

    按位或“|”

    • 将参与运算的两操作数各对应的二进制位进行操作,只有对应的两个二进制位都为0时,结果的对应二进制位才是0,否则为1
    • 通常用来将某变量中的某些位置1且保留其他位不变

    按位异或“^”

    • 将参与运算的两操作数各对应的二进制位进行异或操作,只有对应的两个二进制位不相同时,结果的对应二进制位才是1,否则为0
    • 通常用来将变量中的某些位取反且保留其他位不变
    • 特点:如果有ab=c➡️cb=a 、c^a=b
    • 能实现不通过临时变量就交换两个变量的值
    int a = 5, b = 7;
    a = a ^ b;
    b = b ^ a;
    a = a ^ b;
    

    按位非“~”

    • 将操作书中的二进制位0变成1,1变成0

    左移运算符“<<”

    • a << b
    • 将a的各二进制位全部左移b位后得到的值。左移时,高位丢弃,低位补0,a本身的值不会被更改
    • 左移1位就等于*2,左移n位就等于* $2^n$

    右移运算符“>>”

    • a >> b
    • 将a的各二进制位全部右移b位后得到的值。右移时,移出右边的位会被丢弃,a本身的值不会被更改
    • 对于有符号数,右移时,符号位将一起移动
    • 大多C/C++编译器规定,如果原符号位位1,右移时高位补1,否则补0。
    • 左移1位就等于/2,左移n位就等于/ $2^n$,并且将结果往小取整

    引用

    引用的概念

    • 下面的写法定义了一个引用,并将其初始化为引用某个变量

      类型名 & 引用名 = 某变量名

    int n = 4;
    int & r = n; //r引用了n,r的类型是int &
    
    • 某个变量的引用,等价于这个变量,相当于该变量的一个别名

    • 定义引用时一定要将其初始化成引用某个变量

    • 初始化后,他就一直引用该变量,不会再引用别的变量了

    • 引用只能引用变量,不能引用常量和表达式

    引用的应用

    • C语言中,如何编写交换两个整形变量值的函数?
    void swap(int a, int b)
    {
     int tmp;
     tmp = a; a = b; b = tmp;
    }
    int n1,n2;
    swap(n1,n2); //n1n2的值不会被交换,形参的改变无法影响实参
    
    void swap(int *a,int *b)
    {
     int tmp;
     tmp = *a; *a = *b; *b = tmp;
    }
    int n1,n2;
    swap(&n1,&n2); //n1n2的值被交换,但是多了很多符号,比较麻烦
    
    void swap(int &a, int &b)
    {
      int tmp;
      tmp = a; a = b; b = tmp;
    }
    int n1,n2;
    swap(n1,n2); //n1n2的值被交换,由于a和b是n1和n2的引用,因而可以直接修改
    
    • 引用作为函数的返回值
    int n = 4;
    int & SetValue() {return n;}
    int main()
    {
      SetValue() = 40; //函数的返回值是引用,就可以把函数写在等号左边,可以直接赋值
      cout<<n;//输出:40
      return 0;
    }
    

    常引用

    • 定义引用时,前面加const关键字,即为“常引用”
    int n;
    const int & r = n;
    //r的类型是const int &
    
    • 特点:不能通过常引用去修改其引用的内容

    常引用和非常引用的转换

    • const T & 和 T &是不同的类型(T为int,char等类型)
    • T & 类型的引用或T类型的变量可以用来初始化const T & 类型的引用
    • const T 类型的常变量和const T & 类型的引用则不能用来初始化 T & 类型的引用,除非进行强制类型转换

    const关键字的用法

    1. 定义常量
    const int MAX_VAL = 23;
    const double Pi = 3.14;
    
    1. 定义常量指针
      • 不可通过常量指针修改其指向的内容
      • 不能把常量指针赋值给非常量指针,反过来可以
      • 函数参数为常量指针时,可避免函数内部不小心改变参数指针所指地方的内容
    int n,m;
    const int *p = &n;
    *p = 5; //编译错误
    n = 4; //正确
    p = &m; //正确,可以改变常量指针指向的对象
    
    const int * p1; int * p2;
    p1 = p2;//正确
    p2 = p1;//错误
    p2 = (int *)p1;//正确,通过强制类型转换
    
    void MyPrintf(const char *p)
    {
      strcpy(p,"this");//编译错误
      printf("%s",p);//正确
    }
    
    1. 定义常引用
      • 不能通过常引用修改其引用的变量
    int n;
    const int & r = n;
    r = 5;//编译错误
    n = 4;//正确
    

    动态内存分配

    用new运算符实现动态内存分配

    • 第一种用法,分配一个变量
      • P = new T;
      • T是任意类型名,P是类型为**T ***的指针
      • 动态分配出一片大小为sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P
    int *pn;
    pn = new int;
    *pn = 5;
    
    • 第二种用法,分配一个数组
      • P = new T[N];
      • T:任意类型名
      • P:类型为T *的指针
      • N:要分配的数组元素的个数,可以是整形表达式
      • 动态分配出一片大小为N*sizeof(T)字节的内存空间,并将该内存空间的起始地址赋值给P
    • 动态分配数组实例
    int *pn;
    int i = 5;
    pn = new int[i * 20];
    pn[0] = 20;
    pn[100] = 30;//虽然编译正确,但运行时会出现数组越界
    
    • new 运算符的返回值类型
      • new T; new T[n];
      • 这两个表达式返回值的类型都是 T*
      • int *p = new int;

    用delete运算符释放动态分配的内存

    • 用“new”动态分配的内存空间,要用“delete”运算符进行释放
      • delete 指针; //该指针必须指向new出来的空间
    int *p = new int;
    *p = 5;
    delete p;
    delete p; //导致异常,一片空间不能够被delete多次
    

    用delete运算符释放动态分配的数组

    • 用“delete”释放动态分配的数组,要加“[]”
      • delete [] 指针; //该指针必须指向new出来的数组
    int *p = new int[20];
    p[0] = 1;
    delete [] p;
    

    内联函数,函数重载和函数缺省参数

    内联函数

    • 函数调用存在时间开销。如果函数本身只有几条语句且执行非常快,而且函数被反复执行多次,相比其运行时间,调用函数所产生的时间开销就会很大。

    • 为了减少该开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。

    • 在函数定义前面加“inline”关键字,即可定义内联函数

    • 缺点是可执行程序的体积会增大

    函数重载

    • 一个或多个函数,名字相同,然而参数个数参数类型不相同,这叫做函数重载

      • 以下三个函数是重载关系:

        int Max(double f1,double f2){ }
        int Max(int n1,int n2){ }
        int Max(int n1,int n2,int n3){ }
        
      • 函数重载简化函数命名

      • 编译器根据调用语句中的实参的个数和类型判断应该调用哪个函数

    函数缺省参数

    • C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值
    void func(int x1, int x2 = 2, int x3 = 3){ }
    
    func(10);//等效于func(10,2,3)
    func(10,8);//等效于func(10,8,3)
    func(10,,8);//编译错误,只能最右边的连续若干个参数缺省
    
    • 函数参数可缺省的目的在于提高程序的可扩充性
    • 如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数

    面向对象程序设计方法

    结构化程序设计

    • 复杂的大问题➡️层层分解/模块化➡️若干子问题
    • 自顶向下,逐步求精
    • 程序 = 数据结构(变量)+算法(函数)
    • 在结构化程序设计中,数据结构和算法没有直接关系
    • 遇到的问题
      • 理解难
      • 修改难
      • 查错难
      • 重用难

    面向对象的程序设计

    • 软件设计的目标:更快,更正确,更经济
    • 面向对象的程序设计 = 类 + 类 + …… + 类
    • 设计程序的过程➡️设计的过程
    • 对一类事物进行抽象,提炼出共同属性(数据结构)和行为(函数),将数据结构和算法封装(捆绑)在一起,变成类。

    面向对象语言的发展历程

    • 第一个面向对象语言:Simula

      • 1967年发布Simula 67
      • 提出了类(class)和子类(subclass)的概念
    • 第二个面向对象语言:Smalltalk

    • 1983年 C++

    • 1995年 JAVA

    • 2003年 C#

    C++标准的发展

    • 1989年 C++2.0
    • 1994年 ANSI C++
    • 1998年 C++98
      • 加入STL(Standard Template Library)-泛型设计
    • 2003年 C++03
    • 2011年 C++11
    • 2014年 C++14
    • 2017年 C++17
    • 2020年 C++20

    从客观事物抽象出类

    • 写一个程序,输入矩形的宽和高,输出面积和周长
      • 矩形的属性——宽和高两个变量
      • 矩形的操作——设置宽和高,计算面积计算周长
    • 类的成员=成员变量+成员函数
    • 类就是一个带函数的结构体
    • 类定义的变量➡️类的实例➡️对象
    class CRectangle{
      public:
      int w,h;
      
      void Init(int w_, int h_)
      {
        w = w_; h = h_;
      }
      int Area()
      {
        return w*h;
      }
      int Perimeter()
      {
        return 2*(w+h);
      }
    };
    
    int main()
    {
      int w,h;
      CRectangle r; //r是一个对象
      cin>>w>>h;
      r.Init(w,h);
      cout<<r.Area()<<endl<<r.Perimeter();
      return 0;
    }
    
    • 对象的内存分配

      • 对象的内存空间
        • 对象的大小 = 所有成员变量的大小之和
        • e.g. CRectangle类的对象,sizeof(CREctangle) = 8
      • 每个对象各有自己的存储空间
        • 一个对象的某个成员变量被改变,不会影响到其他的对象
    • 对象间的运算

      • 对象之间可以用‘=’进行赋值
      • 不能用== != > < >= <=进行比较
        • 除非这些运算符经过了“重载“
    • 访问类的成员变量和成员函数

      • 用法1:对象名.成员名
      • 用法2:指针->成员名
      • 用法3:引用名.成员名
    • 类的成员函数的另一种写法

      • 成员函数体和类的定义分开写,在类内只声明,在类外详细定义(要加::)

    类成员的可访问范围

    • 关键字——类成员可被访问的范围
      • private:指定私有成员,只能在成员函数内被访问
      • public:指定公有成员,可以在任何地方被访问
      • protected:指定保护成员
    • 三种关键字出现的次数和先后次序都没有限制
    • 如果缺省,就默认为私有成员
    • 对象成员的访问权限
      • 类的成员函数内部,可以访问:
        • 当前对象的全部属性和函数
        • 同类其他对象的全部属性和函数
      • 类的成员函数以外的地方
        • 只能访问该类对象的公有成员
    • 设置私有成员的目的
      • 强制对成员变量的访问一定要通过成员函数进行
      • 避免程序出错,并且易于对程序进行修改
    • 设置私有成员的机制——隐藏

    编程作业

    Quiz1 简单的学生信息处理程序实现

    #include<iostream>
    #include<stdio.h>
    #include<cstring>
    #include<string>
    #include<string.h>
    //虽然我本地编译用最上面那个头文件就过了,但上传上去好像必须要加上后面这四个才能通过……行吧
    using namespace std;
    class student{
    private:
        char name[10];
        int age;
        char number[10];
        unsigned int gradeAverage;
    
    public:
        void gradeCalculate(unsigned int a,unsigned int b,unsigned int c,unsigned int d)
        {
            gradeAverage = (a+b+c+d)/4;
        }
        student(const char* name_, int age_, const char* number_)
        {
            strcpy(name,name_);
            age = age_;
            strcpy(number,number_);
        }
        void getAll()
        {
            cout << name <<',' << age << ',' << number << ',' << gradeAverage << endl;
        }
    };
    
    int main()
    {
        char name[10] = {''},number[10] = {''};
        int age;
        unsigned int grade1,grade2,grade3,grade4;
        //此处的cin.get()用于把逗号吞掉
        cin.getline(name,10,',');
        cin >> age;
        cin.get();
        cin.getline(number,10,',');
        cin >> grade1;
        cin.get();
        cin>> grade2;
        cin.get();
        cin >> grade3;
        cin.get();
        cin >> grade4;
        
        student* a = new student(name,age,number);
        a->gradeCalculate(grade1,grade2,grade3,grade4);
    
        a->getAll();
        delete(a);
        return 0;
    }
    
  • 相关阅读:
    【lc-database】595. 大的国家
    Visual Studio 2010软件安装教程
    Win10系统下安装VC6.0教程
    HTTP协议
    正则表达式
    类装饰器
    装饰器工厂函数
    装饰器函数
    闭包
    web服务器
  • 原文地址:https://www.cnblogs.com/maimai-d/p/12879933.html
Copyright © 2020-2023  润新知