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] = {'\0'},number[10] = {'\0'};
    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;
}
posted @ 2020-05-13 01:07  maimai_d  阅读(162)  评论(0编辑  收藏  举报