【学习笔记 Day03】C++基础:揭开面向对象的神秘面纱,进一步探索代码的世界

函数基础

函数的定义与使用

在每一新建的项目中,第一个接触的函数始于 main 函数,他是 C++ 的主函数。

  • 函数的定义:
类型说明符  函数名([形式参数列表])
{
      函数体;
}

//形式参数列表:数据类型1 变量名1,数据类型2 变量名2,数据类型3 变量名3,....,数据类型n 变量名n
//[]表示:可有可无
  • 函数的返回值和返回值类型:函数可以有一个返回值,函数的返回值是需要返回给主调函数的处理结果。
    • 语法:return 表达式;
    • 除了指定函数的返回值外,return 语句华友一个作用,就是结束当前函数的执行。
  • 函数的调用:
    • 注意:在定义了一个函数之后,可以直接调用这个函数,但如果希望在定义一个函数前调用它,则需要在调用函数之前添加该函数的函数原型声明。语法格式:类型说明符 函数名(含类型说明的形参列表);
    • 声明了函数原型之后,便可以调用其函数。语法:函数名(实参列表);
//声明函数原型
//...

//定义在main函数后面的,需要在main函数前面进行原型声明
void init();

void main()
{
      //函数体
      //...
      init();
}

void init()
{
      //函数体
      //...
}

//无需声明函数原型
//...

void init()
{
      //函数体
      //...
}

void main()
{
      //函数体
      //...
      init();
}
  • 嵌套调用:
    • 函数允许嵌套调用。
//例如:
int fun2(int m)
{
      return m * m;
}

int fun1(int i,int j)
{
      return fun2(i) * fun2(j);
}

void main()
{
      cout << "Result is" << fun1(2,3) << endl;
}
  • 递归调用:
    • 函数可以直接或是间接地调用自身,称为递归调用。
//例如:
int fun(int i)
{
      //...
      fun(i)
      //...
}

void main()
{
      cout << "Result is" << fun(2) << endl;
}
  • 函数参数的传递:
    • 值传递:指当发生函数调用时,给形参分配内存空间,并用实参来在初始化形参。
    • 引用传递:是一种特殊类型的变量,可以被认为时另一个变量的别名。
int i,j;
int &ri = i;//建立一个int型的引用ri,并将其初始化为变量i的一个别名
j = 10;
ri = j;//相当于i=j;

注意:

  1. 声明一个引用时,必须同时对他进行初始化,使他指向一个已存在的对象。
  2. 一旦一个引用被初始化后,就不能改为指向其他对象。
void swap(int &a,int &b)
{
      int t = a;
      a = b;
      b = t;
}

void main()
{
      int x = 5,y = 10;
      swap(x,y);
}

内联函数

内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。

语法:

inline 类型说明符 函数名(形参列表)
(
      语句序列;
)

constexpr函数

constexpr函数是指能用于常量表达式的函数。

约定:

  1. 函数的返回类型以及所有的形参类型必须是常量。
  2. 函数体中必须有且仅有一条 return 语句。
constexpr int get_size(){return 20};
constexpr int foo = get_size();//正确:foo是个常量表达式

//如果arg是常量表达式,则len(arg)也是常量表达式
constexpr int len(arg){return get_size() * arg};

带默认形参值的函数

函数在定义是可以预先声明默认的形参值。

注意

  1. 有默认值的形参必须在形参列表的最后。
  2. 在相同的作用域内,不允许在同一个函数的多个声明中对同一个参数的默认值重复定义,即使前后定义的值相同也不行。
int add(int x = 5,int y = 6)
{
      return x * y;
}

void main()
{
      add(10,20);  //200
      add(10);     //60
      add();       //30
}

函数的重载

两个以上的函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数。

类与对象

面向对象的基本特点

  • 抽象:对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。
  • 封装:将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机地结合,形成类,其中的数据和函数都是类的成员。
  • 继承:一段程序能够处理多种类型对象的能力。

类和对象

在面向对象程序设计中,程序模块是由类构成的,类是对逻辑上相关的函数与数据的封装,他是对问题的抽象描述。

  • 类的定义
class 类的名称
{
      public:
            外部接口
      protected:
            保护型成员
      private:
            私有成员
}
  • 类成员的访问控制
    • 访问控制属性有三种:公有类型(public)、私有类型(private)、保护类型(protected)
  • 对象:声明一个对象和声明一个一般变量相同。语法:类名 对象名;
  • 类的成员函数的实现:
返回值类型 类名::成员函数名(形参列表)
{
      函数体;
}
  • 内联成员函数
class 类名
{
      public:
            void 成员函数()//内联函数
            {
                  函数体;
            }
}

构造函数和析构函数

  • 构造函数:构造函数的作用就是在对象被创建时利用特定的值构造对象(构造函数在对象被创建时将被自动调用),将对象初始化为一个特定的状态。
    • 复制构造参数:一种特殊的构造函数,其作用是使用一个已经存在的对象(由复制构造参数的参数指定),去初始化同类的一个新对象。
      • 复制构造函数在以下三种情况会被调用:
        1. 当用类的一个对象去初始化另一个对象时。
        2. 如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。
        3. 如果函数的返回值是类的对象,函数执行完成返回调用者时。
    • 默认构造参数:调用时无须提供参数的构造函数称为默认构造函数。
class 类名
{
      public:
            类名();                          //构造函数
                                            //=default:指示编辑器生成默认的构造函数
            类名() = default;                //默认构造函数
            类名() = delete;                 //删除默认构造函数
            类名(形参列表);                   //默认构造函数
            类名(类名 &对象名);               //复制构造函数
            类名():类名(形参列表);            //委托构造参数:类名() -> 类名(形参列表)
      private:
            数据类型 成员变量1;
            数据类型 成员变量2;
            数据类型 成员变量3;
}

类名::类名(形参列表):成员变量1(形式参数1),成员变量2(形式参数2),...,成员变量n(形式参数n)
{
      函数体;
}

类名::类名(类名 &对象名)
{
      函数体;
}

void 函数1(类名 形参名)(函数体;)

类名 函数2()
(
      return 类名();
)

void main()
{
      //复制构造函数被调用的第一种情况
      类名 对象名1();
      类名 对象名2(对象名1);     //用对象1初始化对象2,复制构造函数被调用
      类名 对象名3 = 对象名1;    //用对象1初始化对象3,复制构造函数被调用
      
      cout << 对象2.成员函数1() << endl;

      //复制构造函数被调用的第二种情况
      //函数的形参为类的对象,当调用函数时,复制构造函数被调用
      函数1(对象名1);

      //复制构造函数被调用的第三种情况
      类名 对象名();
      对象名 = 函数2();
}
  • 析构函数:用来完成对象被删除前的一些清理工作。(在对象的生存期即将结束的时候被自动调用)
class 类名
{
      public:
            类名();
            ~类名();
}
  • 移动构造函数:
    • 复制构造函数通过复制的方式构造新的对象,而很多时候被复制的对象仅作复制之用后销毁,在这时,如果使用移动已有对象而非复制对象将大大提高性能。
    • C++11标准中引入看左值和右值。
    • 左值:位于赋值语句左侧的对象变量
    • 右值:赋值语句右侧的值
float n = 6;

float &lr_n = n;          //对变量n的左值引用
float &&rr_n = n;         //错误,不能将右值引用绑定到左值n上

float &&rr_n = n * n;     //将乘法结果右值绑定到右值引用
float &lr_n = n * n;      //错误,不能将左值引用绑定到乘法结果右值

//使用标准库 utility 中声明提供了 move 函数,将左值对象移动成为右值
float &&rr_n = std:move(n);

基于右值引用的新设定,可以通过移动而不复制实参的高性能方式构建新对象,即移动构造函数。类似于复制构造函数的参数为该类对象的右值引用,在构造中移动源对象资源,构造后源对象不再指向被移动的资源,源对象可重新赋值或者被销毁。

class Mystr
{
      public:
            string s;
            Mystr():s(""){};
            Mystr(string _s):s(std:move(_s)){};
            Mystr(Mystr &&str) noexcept :s(std:move(str.s)){} //告知编译器不会抛出异常,移动构造函数
}

结构体和联合体

结构体和类的唯一区别在于,结构体和类具有不同的默认访问控制属性:在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型;在结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为共有类型。

语法:

//定义语法:

struct 结构体名称
{
      公有成员
protected:
      保护型成员
private:
      私有成员
}

//使用语法:
结构体名称 变量名 = {成员数据1初值,成员数据2初值,成员数据3初值,...};

示例:

struct Studentt
{
      int num;
      string name;
      char sex;
      int age;
}

void main()
{
      Student stu = {97001,"Lin Lin",'F',19}
      cout << stu.name << endl;
}

联合体的全部数据成员共享一组内存单元。

定义语法:

union 联合体名称
{
      公有成员
protected:
      保护型成员
private:
      私有成员
}

例如:

union Mark
{
      char grade;
      bool pass;
      int percent;
}

联合体的一些限制:

  • 联合体的各个对象成员,不能有自定义的构造函数、自定义的析构函数和重载的赋值运算符,不仅联合体的对象成员不能有这些函数,这些对象成员的对象成员也不能有。
  • 联合体不能继承,因而也不支持包含多态。

联合体也可以不声明名称,称为无名联合体。

例如:

class ExamInfo
{
public:
      ExamInfo(string _name,char _grade):name(_name),grade(_grade),mode(GRADE){};
      ExamInfo(string _name,bool _pass):name(_name),pass(_pass),mode(PASS){};
      ExamInfo(string _name,int _percent):name(_name),percent(_percent),mode(PERCENTAGE){};
      void show();

private:
      string name;
      enum
      {
            GRADE,
            PASS,
            PERCENTAGE
      } mode,
      union
      {
            char grade,
            bool pass,
            int percent
      }
};

void ExamInfo::show()
{
      cout << name << ";"
      switch (mode)
      {
            case GRADE:cout << grade;break;
            case PASS:cout << (pass ? "PASS" : "FAIL");break;
            case PERCENTAGE:cout << percent;break;
      }
      cout << endl;
}

int main()
{
      ExamInfo course1("English",'B');
      ExamInfo course2("Calculus",true);
      ExamInfo course3("C++ Programming",85);
      course1.show()
      course2.show()
      course3.show()
}

枚举类型 enum

C++语言包含两种枚举类型:不限定作用域的枚举类型和限定作用域的枚举类型。

声明语法:

enum [枚举名]
{
      变量值列表;
}

//枚举类声明形式如下:
enum class/struct [枚举名]
{
      变量值列表;
}

例如:

enum Weekday {SUN,MON,TUE,WED,THU,FRI,SAT}

//枚举类
enum class Weekday {SUN,MON,TUE,WED,THU,FRI,SAT}

注意

  • 枚举类型的名字是可选的。如果 enum 未命名,我们只能在定义该 enum 时定义它的对象,即需要在 enum 定义右侧的花括号和最后的分号之间提供声明列表(使用逗号隔开)。
  • 如果没有指定 enum 的潜在类型,默认情况下限定作用域的成员类型为 int ,对于不限定作用域的 enum 来说,其枚举成员不存在默认类型。
  • 如果指定了枚举元素的潜在类型,一旦某个枚举元素的值超出了该类型范围,会引发程序错误。

由于不限定作用域的 enum 没有指定成员的默认类型,因此必须显示指定。

语法:

enum 枚举名:变量类型

例如:

enum Weekday: string; //不限定作用域的,必须指定成员类型
enum Weekday;         //限定作用域的,可使用默认成员类型

枚举类型运用说明:

  • 对枚举元素按常量处理,不能对他们赋值。
  • 枚举元素具有默认值,他们依次为:0,1,2,…。
  • 也可以在声明时另行定义枚举元素的值。
enum Weekday {SUN = 7,MON = 1,TUE,WED,THU,FRI,SAT}

//定义SUN为7,MON为1,以后的顺序加1,枚举值可以不唯一。
  • 枚举值可以进行关系运算
  • 整数值不能直接赋值给枚举变量,如需将整数赋值给枚举变量,应进行强制类型的转换。
  • 枚举元素是 const 类型,因此初始化枚举成员是提供的初始值必须是常量表达式。
enum Weekday {SUN,MON,TUE,WED,THU,FRI,SAT}

//显示类型转换
Weekday res = Weekday(1); //结果为 SUN

标识符的作用域与可见性

作用域

作用域是一个标识符在程序正文中有效的区域。

  • 函数原型作用域:在函数原型声明时形式参数的作用范围就是函数原型的作用域。
  • 局部作用域:函数体内声明的变量,其作用域从声明处开始,一直到声明所在的块结束的花括号为止。
  • 类作用域:
    • 如果 X 的成员函数中没有声明同名的局部作用域标识符,那么该函数内可以直接访问成员 m 。
    • 通过表达式 x.m 或者 X::m 。这正是程序中访问对象成员的最基本方法。
    • 通过 ptr -> m 这样的表达式,其中 ptr 为指向 X 类的一个对象指针。
  • 文件作用域:不在前述各个作用域中出现的声明,就具有文件作用域,这样的标识符其作用开始于生命点,结束于文件尾。(具有文件作用域的变量也称为全局变量)
  • 命名空间作用域。
  • 限定作用域的 enum 枚举类。

可见性

程序运行到某一点,能够引用到的标识符,就是该处可见的标识符。

对象的生存期

静态生存期

如果对象的生存期与程序的运行期相同,我们称他具有静态生存期。

特点:他并不会随着每次函数调用而产生一个副本,也不会随着函数返回而失效。即当一个函数返回后,下一次在调用时,该变量还会保持上回的值,即使发生了递归调用,也不会为该变量建立新的副本,该变量会在各次调用间共享。

动态生存期

在局部作用域中声明的具有动态生存期的对象,习惯上也被称为局部生存期对象。局部生存期对象诞生于声明点,结束于声明所在的块执行完毕之时。

//例如:
//...

int i = 1;//全局变量,具有静态生存期

void other()
{
      //静态局部变量,具有全局寿命,局部可见,只有第一次进入函数时被初始化
      static int a = 2;
      static int b;

      //局部变量,具有动态生存期,每次进入函数时都初始化
      int c = 10;
}

类的静态成员

静态数据成员

  • 具有静态生存期
  • 类属性是描述的所有对象共同特征的一个数据项,对于任何对象实例,他的属性值都是相同的。
  • 静态数据成员声明后,只能在类外才能进行赋值!常量静态数据成员在类内进行赋值初始化!
class Point
{
public:
      void add()
      {
            count++;
      }
private:
      static int count;
      constexpr static int origin = 0;//静态常量在类内初始化
}

int Point::count = 0;

void main()
{
      Point p;
      p.add();
}

静态函数成员

  • 静态成员函数可以直接访问该类的静态数据和函数成员。而访问非静态成员,必须通过对象名来访问。
class Point
{
public:
      void add()
      {
            count++;
      }
      static void showCount();
private:
      static int count;
      int x;
      constexpr static int origin = 0;//静态常量在类内初始化
}

int Point::count = 0;

static void Point::showCount()
{
      cout << "showCount is" << count << endl;
}

static void Point::f(Point p)
{
      cout << "x is" << x << endl;//对x的引用是错误的
      cout << "x is" << p.x << endl;//正确
}

void main()
{
      Point p1;
      Point p;
      p.add();

      //直接通过类名调用函数输出对象个数的初始值
      Point::showCount(p1);
      Point::f();
      //也可以通过对象来调用
      p.showCount();
}

类的友元

友元关系提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。

友元函数

友元函数是在类中用关键字 friend 修饰的非成员函数。虽然他不是本类的成员函数,但是他在函数体中可以通过对象名访问类的私有和保护成员。

class Point
{
public:
      Point(int x = 0,int y = 0);
      friend float dist(Point &p);
private:
      int x,y;
}

friend float dist(Point &p)//友元函数的实现
{
      return static_cast<float>(sprt(p.x * p.y))
}

void main()
{
      Point p(5,3);
      cout << dist(p) << endl;//结果:15
}

友元类

同友元函数一样,若A类为B类的友元函数,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护成员。

class A
{
public:
      void display()
      {
            cout << x << endl;
      }
      int getX(){return x;}
      firend class B;
private:
      int x;
}

class B
{
public:
      void set(int i);
      void display();
private:
      A a;
}

void B::set(int i)
{
      a.x = i;
}

注意:

  1. 友元关系是不能传递的。
  2. 友元关系是单向的。
  3. 友元关系是不能被继承的。

共享保护数据

常对象

常对象的数据成员值在对象的整个生存期内不能被改变,即常对象必须进行初始化,而且不能被更新。

声明语法:

const 类型说明符 对象名;
//或
类型说明符 const 对象名;

//例如:
const A a(1,3);

用 const 修饰的类成员

  • 常成员函数(不可以在类内进行修改值):
    • 使用 const 关键字修饰的函数为常成员函数
    • 作用:可以区分重载函数。
    • 声明语法:
类型说明符 函数名(形参列表) const;

//例如:
class A
{
public:
      void display()
      {
            cout << x << endl;
      }
      int getX() const;
private:
      int x;
}

int A::getX() const
{
      return x;
}
  • 常数据成员
    • 常数据成员只能通过构造函数进行赋值。
    • 静态常数据成员在类外说明和初始化。
    • 例如:
class A
{
public:
      A(int x);
      void display()
      {
            cout << x << endl;
      }
private:
      const int x;
      static const int y;
}

//静态常成员数据在类外说明和初始化
const int A::y = 10;

//常数据成员只能通过初始化列表来获得初始值
A::A(int _x):x(_x){};

常引用

如果在声明引用时用 const 修饰,被声明的引用就是常引用。

注意:常引用所引用的对象不能被更新。

class Point
{
public:
      Point(int x = 0,int y = 0);
      friend float dist(Point &p);
private:
      int x,y;
}

friend float dist(Point &p)//友元函数的实现
{
      //static_cast:强制转换
      return static_cast<float>(sprt(p.x * p.y))
}

void main()
{
      Point p(5,3);
      cout << dist(p) << endl;//结果:15
}

多文件结构

  • Point.h:
class Point
{
public:
      Point(int _x = 0,int _y = 0):x(_x),y(_y){ count++; };
      Point(const Point &p);
private:
      int x,y,count;
}
  • Point.cpp:
#include "Point.h"
#include <iostream>

using namespace std;

Point::Point(const Point &p):x(p.x),y(p.y)
{
      count++;
}
  • main.cpp:
//...

void main()
{
      Point p(5,6);
}

//...

外部变量与外部函数

如果一个变量或函数除了在定义他的源文件中可以使用外,还能被其他文件使用,那么就称之为外部变量或函数。

将函数和变量限制在编译单元内:

例如:

namespace {
      int n;
      void f(){
            n++;
      }
}

编译预处理

  • #include:
    • #include <文件名>:按标准方式搜索,文件位于系统目录的 include 子目录下。
    • #include ”文件名“:首先在当前目录中搜索,若没有,再按标准方式搜索。
  • #define和#undef:
    • #define 用来定义符号常量,如:#define PI 3.1415926
    • #undef 用来删除 #define 定义的宏,使之不在起作用。
  • 条件编译指令:
#if 常量表达式
      程序块:当常量表达式非零时编译本程序块
#endif

#if 常量表达式
      程序块:当常量表达式非零时编译本程序块
#else
      程序块:当常量表达式非零时编译本程序块
#endif

#if 常量表达式
      程序块:当常量表达式非零时编译本程序块
#elif 常量表达式
      程序块:当常量表达式非零时编译本程序块
.
.
.
#endif
  • defined 操作符:
    • 是一个预处理操作符,而不是指令,因此不要以#开头
    • 语法:defined(标识符)
    • 若标识符在此前经 #define 定义过,并且未经 #undef 删除,则此上述表达式为非0,反之。
#ifndef MyCPP_H
#define MyCPP_H
...
#endif

//等价于

#if !defined(MyCPP_H)
#define MyCPP_H
...
#endif

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容