首页 > 编程语言 >C++ 基础

C++ 基础

时间:2023-03-08 14:59:07浏览次数:36  
标签:函数 int 成员 基础 C++ 运算符 对象 构造函数

基础

程序语言分为

  • 低级语言(机器语言、汇编语言)
  • 中级语言
  • 高级语言(C、C++等)。

C++ 语言的主要特点

  • 兼容 C 语言
  • 面向对象(继承和多态)
  • 引进了类和对象的概念

C++ 的基本数据类型

  • bool:布尔值
  • char:字符型
  • int:整型
  • float:浮点型
  • double:双精度浮点型

注释的两种方式

  • 单行注释(//
  • 多行注释(/*..*/
1. 头文件和命名空间

包含头文件需要使用 # include 指令,一条指令仅可以包含一个头文件,多个头文件需要使用多条指令。

通常使用尖括号 <> 包含系统头文件,会首先在系统设定的目录中寻找要包含的头文件;使用双引号 "" 包含自定义的头文件,会在当前用户目录下或指令中指定的目录下寻找要包含的头文件。

# include <iostream>
# include "myCustom.h"

常用的头文件:

  • 标准输入/输出流:
  • 标准文件流:
  • 标准字符串处理函数:
  • 标准数学函数:

文件后缀 .cpp 是源程序文件,文件后缀 .h 是头文件。

命名空间的作用是消除同名引起的歧义。

// 定义命名空间
namespace work {  // work 为命名空间名
    // 各种声明
    class Foo {...};  
    func() {...};
};

// 使用方式一
using work::func();

// 使用方式二 推荐
using namespace work;
Foo f;  // 文件头声明后后面可以直接使用
func();
2. 基本的输入/输出

当程序需要进行输入/输出信息时,需要包含头文件

  • cin:使用流提取运算符 >> 从标准输入设备键盘取得数据;

  • cout:使用流插入运算符 << 向标准输入设备屏幕输出信息。

# include <iostream>
using namespace std;

int main() {
    int a, b;
    cin>>a>>b;
    cout<<"a="<<a<<"\tb="<<b<<endl;
    return 0;
}
3. 强制类型转换运算符

static_cast 把表达式类型转换为类型名所指定的类型,static_cast 也可以省略。

double num = 3.12;
one = static_cast<int>(num);  // 强制类型转换
two = int(num);               // 强制类型转换运算符的新形式
three = (int) num;            // 强制类型转换运算符的旧形式
four = num;                   // 自动类型转换
4. 函数参数的默认值

C++ 语言规定,提供默认值必须按从右至左的顺序提供,有默认值的形参必须在最后。

void func(int a, int b=2, int c=3);  // 正确
void func(int a=1, int b);  // 错误
void func(int a, int b=2, int c); // 错误

调用函数时,主调函数的实参与被调函数的形参按从左至右的顺序进行匹配对应。

int func(float x, char y='$', int a=9, char b='@');

// 函数调用判断
func(3.14);  // 正确,仅匹配 x,其他都有默认值
func(3.14, '#');  // 正确,匹配 x、y 
func(3.14, '%', '@');  // 错误,第三个参数类型不对应,预期 int,实际是 char
func(3.14, '&', 5, '*');  // 正确
func(3.14, , 5, '*');  // 错误,调用时的实参应该是连续排列的
5. 引用

引用相当于给变量起了个别名,对应于某个内存地址。如果给某个变量起了别名(不需要给它另开辟内存单元),相当于变量和这个引用都对应到同一地址

// 在程序中定义变量的引用
// 类型名 &引用名 = 同类型的某变量名;
# include <iostream>
using namespace std;

int main() {
    int foo = 1;
    int &ref = foo;  // ref 是 foo 的引用,等价于 foo
	const int &ref2 = foo;  // 定义常引用
    
    ref = 2;  // foo=2; ref=2; ref2=2;
    foo = 3;  // foo=3; ref=3; ref2=3;
    // ref2 = 4;  错误,不能使用常引用对所引用的变量进行修改
    
    return 0;
}

引用还可以用在函数中,既可以作为函数的参数使用,也可以作为函数的返回值使用。函数调用时参数的传递方式有两种:传值传引用

传值,传递对象的。将实参的值拷贝给形参,函数执行过程中,都是对这个拷贝进行操作的,执行完毕后,形参的值并不拷贝回实参。也就是函数内部对形参的改变不会影响到函数外实参的值。

传引用,传递对象的首地址值。函数调用时,实参对象名传递给形参对象名,形参就成为实参的引用,他们是等价的,代表同一个对象。也可以看作是将实参的地址传递给了形参,函数内部对形参进行的改变,会影响到函数外实参的值。

// 函数中使用引用

/* 例 1:引用作为参数传递 */
# include <iostream>
using namespace std;

void func(int x, int y) {  // 传值
    int tmp;
    tmp = x; x = y; y = tmp;
    cout<<"func(): "<<"a="<<x<<" b="<<y<<endl;
}

void func_ref(int &x, int &y) {  // 传引用
    int tmp;
    tmp = x; x = y; y = tmp;
    cout<<"func_ref(): "<<"a="<<x<<" b="<<y<<endl;
}

int main() {
    int a = 10, b = 20;
    
    func(a, b);
    cout<<"调用 func() 后: a="<<a<<" b="<<b<<endl;
    // func(): a=20 b=10
    // 调用 func() 后: a=10 b=20
    // 形参的改变没有影响函数外实参的值
    
    func_ref(a, b);
    cout<<"调用 func_ref() 后: a="<<a<<" b="<<b<<endl;
    // func_ref(): a=20 b=10
    // 调用 func_ref() 后: a=20 b=10
    // 形参的改变影响了函数外实参的值
    
    return 0;
}

/* 例 2:引用作为返回值 */
# include <iostream>
using namespace std;

int a = 10, b = 20;

int &ref(int &x) {  // 返回值是引用
    return x;  
}

int main() {
    ref(a) = 30;
    cout<<"a="<<a<<" b="<<b<<endl;   // a=30 b=20
    
    ref(b) = 40;
    cout<<"a="<<a<<" b="<<b<<endl;  // a=30 b=40
    
    return 0;
}
6. const 与指针共同使用

const 用于约束某值不变,在 C++ 中是用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数。

// const 修饰普通变量
const int a = 10;
int b = a;  // 正确
a = 8;  // 错误 不能改变

a 被定义为一个常量,可以将 a 赋值给 b,但是不能对 a 再次赋值,不允许对常量重新赋值。

const 修饰指针变量

情况一:左定值,const 修饰指针指向的内容,则内容为不可变量。

const int *p = 8;

如果唯一的 const 位于符号 * 的左侧,表示指针所指数据是常量,数据不能通过本指针改变,但可以通过其他方式修改。指针本身是变量,可以指向其他的内存单元。

情况二:右定向,const 指针指向的内存地址不能被改变,但其内容可以改变。

int a = 8;
int *const p = &a;
*p = 9;  // 正确,内容可改变
int b = 7;
p = &b;  // 错误,指针地址不能被改变

如果唯一的 const 位于符号 * 的右侧,表示指针本身是常量,不能让该指针指向其他内存地址。指针所指的数据可以通过本指针进行修改。

情况三:内容和指针内存地址都固定,不可改变。

int a = 8;
const int *const p = &a;
int const *const p = &a;

如果在 * 的左右各有一个 const 时,表示指针和指针所指的数据都是常量,既不能让指针指向其他地址,也不能通过指针修改所指向的内容。

7. 内联函数

为避免频繁的函数调用,使用内联函数,在编译时不生成函数调用,而是将程序中出现的每一个内联函数表达式替换为该内联函数的函数体。使用内联函数会使最终可执行程序的体积增大,以空间消耗节省时间开销。

定义内联函数需要在函数头加上关键字 inline,定义在前,调用在后。内联函数主要应用于代码量少且频繁调用的函数,通常不建议内联函数体中包含循环语句或 switch 语句。

# include <iostream>
using namespace std;

inline int Max(int x, int y) {
    return x > y ? x : y;
}

int main() {
    cout<<Max(20, 10)<<endl;
    cout<<Max(100, 500)<<endl;
}

如果函数成员定义在类体内,则默认是内联函数。也可以在类体内部声明函数,并加上 inline 关键字,然后在类体外给出定义,这样也是内联函数。

# include <iostream>
using namespace std;

class A {
    public:
        inline void print1();  // 类体外定义需要加 inline 关键字
        void print2() {  // 默认内联函数
            cout<<"print inline 2"<<endl;
        }
};

void A::print1() {
    cout<<"print inline 1"<<endl;
}

int main() {
    A a;
    a.print1();
    a.print2();
}
8. 函数重载

函数重载是指在程序的同一范围内声明几个功能类似的同名函数,提高代码可读性。必须要满足条件之一:

  • 参数表中参数类型不同(顺序不同也可)
  • 参数表中参数个数不同
# include <iostream>
using namespace std;

int max(int x, int y) {
    return x > y ? x : y;
}

int max(float x, float y) {
    return x > y ? x : y;
}

int main() {
    cout<<max(5, 8)<<endl;
    cout<<max(3.14, 5.67)<<endl;
}

如果两个函数的名字和参数表都是一样的,仅仅是返回值类型不同,则不符合函数重载的条件,编译报错。

// 错误的函数重载
float max(float x, float y);
int max (float x, float y);

采用引用参数也不符合函数重载。

// 错误的函数重载
void print(double);
void print(&double);

避免产生二义性

// 错误的函数重载
int sum(int a, int b, int c=0);
int sum(int a, int b);

sum(1, 2);  // 编译错误,不知道调用哪个函数
9. 指针和动态内存分配

C++ 中使用 new 运算符实现动态内存分配。指针变量中保存的是一个地址,也称指针指向一个地址。

int *p;
p = new int;  // 动态分配 4 字节的内存空间
*p = 5;

使用 new 运算符也可以动态分配一个任意大小的数组。数组的长度是声明数组时指定的,不允许定义元素个数不明确的数组。

int pArr;
pArr = new int[5];  // 分配了 5 个元素的整型数组
pArr[0] = 10;  // 数组的第一个值
pArr[4] = 20;  // 数组的最后一个值

使用 pArr[-1] 或者 pArr[5]时,下标会越界。不过在编译时,对于数组越界的错误不会提示,运行时报错。

使用 new 运算符动态申请的内存空间,需要在使用完毕后释放。使用 delete 运算符,用来释放动态分配的内存空间。

/* 释放指针变量动态内存 */
int foo = 6;
int *p = &foo;
delete p;  // 错误,delete 后面的指针必须是指向动态分配的内存空间(new)

int *q = new int;
*q = 8;
delete q;  // 正确,q 指向动态分配的空间

/* 释放数组动态内存 */
int *p = new int[100];
delete []p;
10. string 对象

C++ 标准模板库中提供了 string 数据类型,专门处理字符串。string 是一个类,这个类型的变量称为 string 对象

# include <string>  // 需包含头文件

// 使用 string 类型初始化变量
string str = "hello";
string str2 = "world";

// 使用字符数组对 string 变量初始化
char name[] = "hello, world.";
string str = name;

// 声明 string 对象数组
string citys[] = {"beijing", "shenzhen", "shanghai"};

str.empty()           // 判断字符串是否为空 true false
str.length()          // 返回字符串长度
str.size()            // 返回字符串占用空间字节数
str.append("haha")    // 向字符串后面追加内容
str.insert(4, "123")  // 从字符串第四个位置插入内容

string 对象间可以相互赋值,不需要考虑空间是否足够的问题。

11. C++ 语言的程序结构

C++程序以 .cpp 作为文件扩展名,文件中包含若干个类和若干个函数。程序中必须有且仅有一个主函数 main(),这是程序执行的总入口。程序从主函数的开始处执行,直到结束。主函数可以出现在任何地方。

程序的结束通常是遇到了以下两种情况:

  • 主函数中遇到了 return 语句
  • 执行到了主函数最后的括号

主函数可以调用其他函数,但其他函数不能调用主函数。主函数仅是系统执行程序时调用的。

面向对象的基本概念

结构化程序设计方法采用自顶向下、逐步求精及模块化思想,大问题化小问题。编写程序时使用 3 种基本控制结构:顺序、选择、循环,强调程序的易读性。

面向对象程序设计方法就是使 分析、设计和实现一个系统的方法 尽可能地接近 人们认识一个系统的方法。通常包括三方面:面向对象的分析、面向对象的设计、面向对象的程序设计

对象具有两个特性:

  • 状态,指对象本身的信息(属性);
  • 行为,指对对象的操作。

通过对实物的抽象找出同一类对象的共同属性(静态特征)行为(动态特征),从而得到类的概念。对象是类的一个具象,类是对象的一个抽象。C++ 中使用 对象名、属性、操作 三要素来描述对象。

面向对象的程序设计有四个基本特点:

  • 抽象:对象的属性和操作

  • 封装:通过自定义类来支持数据封装和信息隐藏

  • 继承:在已有类的基础上加上特殊的数据和函数构成新类,原来的类是基类(父类或超类),新类是派生类(子类)

  • 多态 :不同种类的对象具有名称相同的行为,但具体的实现方式却不同。通过函数重载及运算符重载实现的多态。

1. 类的定义

类是具有唯一标识符的实体,类名不能重复。标识符命名规则:字母、数字、下划线的组合,但不能以数字开头,大小写敏感,不能和系统中的关键字重名。类定义以 ; 结束,大括号中的部分称为类体。

定义类时系统并不为类分配存储空间,类中声明的任何成员不能使用 auto、extern、register 关键字进行修饰。

类中的成员按功能划分:

  • 成员变量:对象的属性,个数不限,也称为数据成员。成员变量的声明方式与普通变量的声明方式相同;
  • 成员函数:对象的操作,个数不限,声明方式与普通函数相同。

类中的成员按访问权限划分:

  • 公有成员(public):公有的,可以在程序任何地方访问;
  • 私有成员(private) :私有的,仅能在本类内访问;未定义则默认为私有;
  • 保护成员(protected):保护的,能在本类内及子类中被访问。

成员函数可以定义在类体内,也可以定义在类体外。可以定义不是任何类的成员的函数,称为全局函数

如果成员函数定义在类体外,则类体内必须要有函数原型声明,类体外定义函数必须使用类作用域运算符 ::。成员函数在内存中只有一份,可以作用于不同的对象,为类中各对象共享

# include <iostream>
using namespace std;

class A {
    int foo = 1;  // 定义成员变量,默认为私有成员
    
    public:  // 共有成员
    	void print();  // 类体内声明成员函数
    	A a;  // 错误,不能定义本类的成员变量
};  // 注意类定义最后要加引号

void A::print() {};  // 类体外定义成员函数

2. 创建类对象的基本形式

class Test {
    public:
    	Test();
    	Test(int x);
};

/* 方法一 */
Test t1;            // 类名 对象名;
Test t2(5);         // 类名 对象名(参数);
Test t3 = Test(6);  // 类名 对象名 = 类名(参数);
Test t4, t5, t6(7), t7(10);  // 扩展多个对象

/* 方法二 */
Test *p1 = new Test;     // 类名 *对象指针名 = new 类名;
Test *p2 = new Test();   // 类名 *对象指针名 = new 类名();
Test *p3 = new Test(5);  // 类名 *对象指针名 = new 类名(参数);

new 创建对象时返回的是一个对象指针,指向创建的对象。创建的对象必须用 delete 来撤销。

// 声明对象的引用
Test t1, t2;    // 定义对象
Test &t = t1;  // 声明对象的引用 ==> 类名 &对象引用名 = 对象;
Test *p = &t2;  // 声明对象指针  ==> 类名 *对象指针名 = 对象的地址;
Test ts[3];     // 声明对象数组  ==> 类名 对象数组名[数组大小];

3. 访问对象的成员

定义了类对象后,就可以访问对象的成员。

# include <iostream>
using namespace std;

class Student {
    int age;
    public:
        char msg[40] = "该学生年龄为:";
        int getAge();
        void setAge(int);
};

int Student::getAge() {
    return age;
}

void Student::setAge(int x) {
    age = x;
}
3.1 通过对象访问
  • 对象名.成员变量名
  • 对象名.成员函数名(参数表)
int main() {
    Student s;
    s.setAge(18);  // 成员函数 
    cout<<s.msg;   // 成员变量
    cout<<s.getAge()<<endl; 
    
    return 0;
}
3.2 通过指针访问

还可以使用指针或引用的方式来访问类成员,运算符 . 需要更换为 ->

int main() {
    Student s;
    Student *p = &s;
    p -> setAge(19);
    cout<<p->msg;
    cout<<p->getAge()<<endl;

    return 0;
}
3.3 通过引用访问
// 与通过对象访问方式一样
int main() {
    Student s;
    Student &sr = s;
    sr.setAge(20);
    cout<<sr.msg;
    cout<<sr.getAge()<<endl;
    
    return 0;
}

4. 标识符的作用域与可见性

标识符是组成程序的最小成分之一。类名、函数名、变量名、常量名和枚举类型的取值等都是标识符。标识符的作用域有:

  • 函数原型作用域:函数声明时的形参,这是最小的作用域;
  • 局部作用域(块作用域):代码块内,比如循环语句内变量;
  • 类作用域
  • 命名空间作用域

类作用域有三种访问方式:

  • 该类内的成员函数可以直接访问
  • 在类外,通过类.成员类::成员访问
  • 在类外,通过类指针名->成员访问

具有命名空间作用域的变量称为全局变量。命名空间作用域有两种访问方式:

  • 命名空间名::成员;
  • using 命名空间名::成员;
  • using namespace 命名空间名;

作用域的隐藏规则如下:

  • 标识符声明在前,引用在后;

  • 同一作用域中,不能声明同名标识符;

  • 不同作用域中,可以声明同名标识符;

  • 在具有包含关系的两个作用域中,外层声明的标识符:

    • 如果没有在内层重新声明,外层标识符依然在内层可见;
    • 如果在内层重新声明,则内层标识符隐藏外层同名标识符,这种机制称为隐藏规则

类和对象进阶

1. 构造函数

基本数据类型的变量初始化:

  • 全局变量:声明时没有初始化,则系统自动为其初始化为 0
  • 局部变量:声明时没有初始化,则是一个随机值
构造函数的作用

对象的初始化,需要通过构造函数机制,来为对象成员变量赋初值。构造函数是类中的特殊成员函数,给出类定义时,需要编写构造函数,如果没有,则默认由系统添加一个不带参数的构造函数

声明对象后,使用 new 运算符为对象进行初始化,此时系统自动调用构造函数,完成对象的初始化工作,保证对象的初始状态是确定的。

构造函数的定义

定义一个类时,需要为类定义相应的构造函数。构造函数的函数名与类名相同,没有返回值。一个类的构造函数可以有多个,允许重载,参数表一定不能完全相同

当类中没有定义任何构造函数时,系统会自动添加一个参数表和函数体都为空的默认构造函数。因此,任何类都保证至少有一个构造函数

class myDate {};

// 定义构造函数 方式一 无参数
myDate::myDate() {
    year = 1970; month = 1; day = 1;
}

// 方式二 有参数 函数体内赋值
myDate::myDate(int y, int m, int d) {
    year = y; month = m; day = d;
}

// 方式三 另一种写法
myDate::myDate(): year(1970), month(1), day(1) {}  // 赋初始值
myDate::myDate(int y, int m, int d): year(y), month(m), day(d) {}  // 从参数列表取值
构造函数的使用

创建类的任何对象时都一定会调用构造函数进行初始化。如果程序中声明了对象数组,那么数组的每个元素都是一个对象,每个元素都要调用构造函数进行初始化。如果通过类仅声明了指针,并未与对象相关,则不会调用构造函数。

// Test 是类
// 调用 4 次构造函数,声明指针不会调用
Test a(4), b[3], *p;
复制构造函数

复制构造函数是构造函数的一种,也称为拷贝构造函数。作用是使用一个已存在的对象去初始化另一个正在创建的对象。例如:类对象间的赋值是由复制构造函数实现的。

复制构造函数只有一个参数,参数类型是本类的引用。一个类中可以写两个复制构造函数,函数的参数分别为 const 引用和非 const 引用。

以下三种情况会自动调用复制构造函数:

  • 用一个对象去初始化另一个对象
  • 作为函数形参的对象
  • 作为函数返回值的对象
class A{
    public:
        int x;
        A(int t) {x = t;}  // 无参构造函数
        A(A &t) {x = t.x;}  // 复制构造函数一
        A(const A &t) {x = t.x;};  // 复制构造函数二
};

int main() {
    A a(10);
    cout<<a.x<<endl;  // 10
    A b(a);
    cout<<b.x<<endl;  // 10
    return 0;
}

2. 析构函数

析构函数也是成员函数的一种,名字与类名相同,但要在类名前加一个 ~ 符号,以区别构造函数。析构函数没有参数,也没有返回值。一个类中有且仅有一个析构函数。如果未定义,则系统自动生成函数体为空的默认析构函数。

析构函数的作用是做一些善后处理的工作,当对象消亡时自动调用析构函数。比如通过 new 创建的对象,使用 delete 释放空间时,首先调用对象的析构函数,然后再释放对象占用的空间。

对于对象数组,要为它的每个元素调用一个构造函数和析构函数。析构函数的调用执行顺序与构造函数正好相反

# include <iostream>
using namespace std;

class Test {
    public:
        Test();
        ~Test();
    private:
        int *p;
};

Test::Test() {
    cout<<"Test 构造函数"<<endl;
    p = new int[10];  // 指针指向堆空间
}

Test::~Test() {
    cout<<"Test 析构函数"<<endl;
    delete p;  // 必须显式的声明析构函数,释放空间,避免内存泄漏
}

int main() {
    Test t;
    return 0;
}

3. 变量及对象的生存期和作用域

全局变量

  • 未赋初值默认为 0,字符型变量为空字符
  • 作用域:定义在函数外,可被所有文件的函数使用,其他文件使用需 extern 声明(外部链接)
  • 生存期:整个程序执行期
  • 不同文件的全局变量不可以重名

局部变量

  • 未赋初值,内容为随机
  • 作用域:程序块内
  • 生存期:程序块执行期
  • 同一文件中全局变量和局部变量可以重名,在局部变量作用域内,全局变量不起作用

静态全局变量

  • 值只初始化一次,未赋初值默认为 0,字符型变量为空字符
  • 作用域:本文件内,存储在全局数据区
  • 生存期:整个程序执行期
  • 不同文件的静态全局变量可以重名

静态局部变量

  • 值只初始化一次,未赋初值默认为 0,字符型变量为空字符
  • 作用域:程序块内,存储在全局数据区
  • 生存期:整个程序执行期

使用 new 创建的变量具有动态生存期,从声明处开始,直到用 delete 释放存储空间或程序结束。

类对象的生存期为调用构造函数开始到消亡时调用析构函数。

4. 类的静态成员

类的静态成员分为:

  • 静态成员变量
  • 静态成员函数

类的静态成员只有一份保存在公用内存中,被类的所有对象共享。静态成员定义时,需要在前面添加 static 关键字。必须在类体外赋静态成员变量的初值。

# include <iostream>
using namespace std;

class Book {
    public:
        static int page_num;  // 静态数据成员
        static void print() {  // 静态函数才能调用静态变量
            cout<<"已阅读到的页码为:"<<page_num<<endl;
        }
};

int Book::page_num = 100;  // 静态数据成员的初值只能在类体外定义,不需要加 static 关键字

int main() {
    Book b1, b2, *b3;

    b1.print();  // 100
    b2.print();  // 100 该类的所有对象公用一个静态数据成员

    // 静态成员访问的三种方法
    cout<<Book::page_num<<endl;  // 类名.静态成员名
    cout<<b1.page_num<<endl;     // 对象.静态成员名
    cout<<b3->page_num<<endl;    // 对象指针->静态成员名
    return 0;
}

类的静态函数只能处理类的静态成员变量。静态函数与静态函数之间、非静态函数与非静态函数之间是可以相互调用的,非静态成员函数内可以调用静态成员函数,但静态成员函数内不能调用非静态成员函数。

5. 常量成员和常引用成员

在类中,可以使用关键字 const 定义成员变量、成员函数、类的对象。

类的常量成员变量必须进行初始化,且只能通过构造函数的成员初始化列表的方式进行。

定义常量成员变量或常量对象:const 数据类型 常量名 = 表达式;

定义常量函数:类型说明符 函数名(参数表) const;

对象被创建以后,常量成员变量的值不允许被修改,只可以读其值。对于常量对象,只能调用常量函数

class A {
    public:
    	void test() {}  // 非常量成员函数
};

int main() {
    const A a;
    a.test();  // 错误 常量对象不能调用非常量成员函数
}

6. 成员对象和封闭类

一个类的成员变量如果是另一个类的对象,则该成员变量称为成员对象。这两个类为包含关系,包含成员对象的类叫做封闭类

# include <iostream>
using namespace std;

class Tyres {
    private:
        int radius, width;
    public:
        Tyres(int r, int w): radius(r), width(w) {
            cout<<"Tyres(radius="<<radius<<", width="<<width<<")"<<endl;
        };
};

class Car {
    private:
        int prices;
        Tyres tyres;
    public:
        Car(int p, int tr, int td);
};

// 定义封闭类构造函数中,需要指明调用成员对象的哪个构造函数 如:Tyres(int, int)
Car::Car(int p, int tr, int td): prices(p), tyres(tr, td) {
    cout<<"Gogogo!"<<endl;  // 先调用成员对象的构造函数,在调用封闭类对象的构造函数
}

int main() {
    Car car(100, 3, 6);
    return 0;
}

// Tyres(radius=3, width=6)
// Gogogo!

7. 友元函数

设置私有成员的机制叫做隐藏。修改私有属性需要通过公有函数,函数内可以避免对对象的不正确操作或做一些其他修改。私有类型的成员在类外不能访问,通过类内公有函数可以访问但是比直接访问的效率低,所以提供了友元访问方式。

友元函数内部可以直接访问本类对象的私有成员,友元函数不是类的成员函数,但允许访问类中的所有成员。不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,结果是一样的。友元的概念破坏了类的封装性和信息隐藏,但有助于数据共享,能够提高程序执行的效率。

友元函数使用关键字 friend 标识,定义方式 :

  • friend 返回值类型 函数名(参数表);

  • friend 返回值类型 类名::函数名(参数表);

一个函数可以声明为多个类的友元函数,一个类中也可以有多个友元函数。

友元类

如果将一个类 B 说明为类 A 的友元类,则类 B 中的所有函数都是类 A 的友元函数,在类 B 的所有成员函数中都可以访问类 A 中的所有成员。

声明格式为:friend class 类名;

友元类的关系是单向的,友元类的关系不能传递。一般不把整个类说明为友元类。

8. this 指针

当调用一个成员函数时,系统自动向它传递一个隐含的参数,该参数是一个指向调用该函数的对象的指针,称为 this 指针,从而使成员函数知道对哪个对象进行操作。

  • 非静态成员函数内部可以直接使用 this 关键字,代表指向该函数所作用的对象的指针

  • 静态成员函数没有 this 指针

  • 一般情况下,可以省略 this->,系统采用默认设置

运算符重载

运算符重载的概念

算术运算符包括:+ - * / %,通常只能用于对基本数据类型的常量或变量进行运算,而不能用于对象之间的运算。运算符重载可以使运算符也能用来操作对象

可重载的运算符不可重载的运算符

重载运算符有一个返回类型和一个参数列表,这样的函数称为运算符函数。运算符可以被重载为全局函数,也可以被重载为类的成员函数。声明为全局函数时,通常应是类的友元。运算符函数是一种特殊的友元函数或成员函数

重载运算符的规则
  • 符合原有的用法习惯
  • 不能改变运算符原有的语义
  • 不能改变运算符操作数的个数及语法结构
  • 不能创建新的运算符
  • 重载运算符() [] -> = 时,只能重载为成员函数,不能为全局函数
  • 不能改变运算符用于基本数据类型对象的含义
myComplex operator+(const myComplex & c1, const myComplex & c2) {
	return myComplex(c1.real + c2.real, c1.imag + c2.imag); 
}
myComplex operator+(const myComplex& c1, double r) {
	return myComplex(c1.real + r, c1.imag); 
}
myComplex operator+(double r, const myComplex& c1) {
	return myComplex(r + c1.real, c1.imag); 
}

myComplex operator-(const myComplex& c1, const myComplex& c2) {
	return myComplex(c1.real - c2.real, c1.imag - c2.imag); 
}
myComplex operator-(const myComplex& c1, double r) {
	return myComplex(c1.real - r, c1.imag); 
}
myComplex operator-(double r, const myComplex& c1) {
	return myComplex(r - c1.real, -c1.imag); 
}
重载赋值运算符

赋值运算符 = 只能重载为成员函数。

myComplex& myComplex::operator=(const myComplex& c1) {
	this->real = c1.real; 
    this->imag = c1.imag; 
    return *this;
}
myComplex& myComplex::operator=(double r) {
	this->real = r; 
    this->imag = 0; 
    return *this;
}

同类对象之间可以通过赋值运算符进行赋值。如果没有经过重载,= 的作用就是将赋值号右侧对象的值一一赋值给左侧的对象。这相当于值的拷贝,称为浅拷贝。重载赋值运算符后,赋值语句的功能是将一个对象中指针成员变量指向的内容复制到另一个对象指针成员变量指向的地方,这样的拷贝叫深拷贝

重载流插入运算符和流提取运算符
  • 流插入运算符(cout) <<
  • 流提取运算符(cin) >>
#include <iostream.h> 

class Test
{
	private:
		int i; float f; char ch;
	public:
		test(int a=0, float b=0, char c='\0') {i=a; f=b; ch=c;} 
    	friend ostream &operator<<(ostream &, test);    // 必须重载为类的友元
    	friend istream &operator>>(istream &, test &);  // 必须重载为类的友元
};

ostream &operator<<(ostream &stream, test obj)
{
	stream<<obj.i<<",";  // stream 是 cout 的别名  
    stream<<obj.f<<",";
    stream<<obj.ch<<endl;
	return stream;
}

istream &operator>>(istream &t_stream, test &obj)
{
	t_stream>>obj.i;  // t_stream 是 cin 的别名 
    t_stream>>obj.f;
	t_stream>>obj.ch;
	return t_stream;
}

void main() {
    test A;
    operator<<(cout, "Input as i f ch:");
    operator>>(cin, A);  // 45,8.5,'W’
    operator<<(cout,A);  // 45,8.5,'W’
    
    return 0;
}
重载自增、自减运算符
  • 自增运算符:++k k++

  • 自减运算符:--k k--

按照定义,++k 返回被修改后的值,k++ 返回被修改前的值。

# include <iostream>
using namespace std;

class Demo {
    private:
        int n;
    public:
        Demo(int i=0): n(i) {}
        operator int() {return n;}
        Demo & operator++();   // 用于前置形式
        Demo operator++(int);  // 用于后置形式
};

Demo &Demo::operator++() {
    n++;
    return *this;
}

Demo Demo::operator++(int k) {
    Demo tmp(*this);  // 记录修改前的对象
    n++;
    return tmp;  // 返回修改前的对象
}

int main() {
    Demo d(10);
    // 后置形式两种写法
    cout<<(d++)<<endl;  // 10
    cout<<d<<endl;      // 11
    d.operator++(0);    // 11 不输出 有参代表后置形式
    cout<<d<<endl;      // 12

    // 前置形式两种写法     
    cout<<(++d)<<endl;   // 13 
    d.operator++();      // 14 不输出 无参代表前置形式
    cout<<d<<endl;       // 14

    return 0;
}

类的继承与派生

通过已有的类建立新类的过程,叫做类的派生。

  • 原来的类称为基类、父类、一般类
  • 新类称为派生类、子类、特殊类

派生类继承于基类,基类派生了派生类,派生类可以作为基类再次派生新的派生类,这种集合称作类继承层次结构。

使用基类派生新类时,除构造函数和析构函数外,基类的所有成员自动成为派生类的成员,包括基类的成员变量和成员函数。派生类中需要定义自己的构造函数和析构函数,可以增加基类中没有的成员,还可以重新定义或修改基类中已有的成员,包括可以改变基类中成员的访问权限。

// 基类与派生类的定义
class Base {
    int a, b;
};

class Derived: public Base {
    int c;
};

派生类占用的存储空间大小,等于基类成员变量占用存储空间大小 加上 派生类对象自身成员变量占用的存储空间大小。对象占用的存储空间包含对象中各成员变量占用的存储空间。可以使用 sizeof() 计算对象占用的字节数。

基类有友元,派生类不会继承友元类或友元函数。如果基类是某类的友元,那么这种友元关系是继承的。如果基类中的成员是静态的,在派生类中静态属性随静态成员被继承。如果基类的静态成员是公有的或者保护的,则他们被其派生类继承为派生类的静态成员。

访问控制

各成员 派生类中 基类与派生类外
基类的公有成员 直接访问 直接访问
基类的保护成员 直接访问 调用公有函数访问
基类的私有成员 调用公有函数访问 调用公有函数访问
从基类继承的公有成员 直接访问 直接访问
从基类继承的保护成员 直接访问 调用公有函数访问
从基类继承的私有成员 调用公有函数访问 调用公有函数访问
派生类中定义的公有成员 直接访问 直接访问
派生类中定义的保护成员 直接访问 调用公有函数访问
派生类中定义的私有成员 直接访问 调用公有函数访问

类型兼容规则

在公有派生的情况下,有以下三条兼容规则:

  • 派生类的对象可以赋值给基类对象;
  • 派生类的对象可以用来初始化基类引用;
  • 派生类对象的地址可以赋值给基类指针,即派生类的指针可以赋值给基类的指针。

派生类的构造函数与析构函数

类之间的关系

多层次的派生

基类与派生类指针的相互转换

输入/输出流

1. iostream 类库

C++ 的 iostream 类库提供了数百种 I/O 功能,常见的头文件有如下三个。

iostream

头文件 iostream 包含操作所有输入/输出流所需的基本信息,含有 4 个标准流对象:

  • cout:标准输出流,与标准输出设备(显示器)相关联,可以被重定向为向文件里写入数据;
  • cin:标准输入流,与标准输入设备(键盘)相关联,可以被重定向为从文件中读取数据;
  • cerr:输出错误信息,与标准错误信息输出设备(显示器)相关联(非缓冲),不能被重定向;
  • clog:输出错误信息,与标准错误信息输出设备相关联(缓冲),不能被重定向。

iomanip

包含格式化 I/O 的带参数流操纵符,可用于指定数据输入/输出的格式。例如:

  • setw(int w):指定输出宽度为 w 个字符,或输入字符串时读入 w 个字符,一次有效。
  • setprecision():设置有效数字位数
  • setfill():指定输出宽度,宽度不足时用空格填充
  • setbase():输入表示数值进制的前缀
  • setiosflags():设置标志字

不带参数的流操纵符:

  • endl(O):换行符,输入一个换行符,清空流
  • ends(O):输出字符串结束,清空流
  • flush(O):清空流缓冲区
  • dec(I/O,默认):十进制形式
  • hex(I/O):十六进制形式
  • oct(I/O):八进制形式
  • ws(O):提取空白字符

fstream

包含处理文件的有关信息,提供建立文件、读写文件的各种操作接口。

标签:函数,int,成员,基础,C++,运算符,对象,构造函数
From: https://www.cnblogs.com/abeelan/p/17191963.html

相关文章

  • 网络基础及抓包方法汇总
    网络基础常见网络模型与常见协议网络架构图互联网通讯协议架构TCP/UDPTCP:面向连接、错误重传、拥塞控制、适用于可靠性高的场景UDP:不需要提前建立连接,实现简单......
  • python自动化基础-花样输出
    彩色输出程序deflog():w='\x1b[1;33;40m'+'内容'+'\x1b[0m'print(w)v='\033[0;32;40m'+'内容'+'\033[0m'print(v)if__name__=......
  • C++自定义比较函数的bug
    autocmp=[](intx,inty){returntrue;};priority_queue<int,vector<int>,cmp>q;报错第三个参数失配,需要一个类型,而不是对象。平时用sort直接传cmp习惯了,才发......
  • 【Docker-基础使用:安装,启用,基本命令,数据卷】
    目录:一、认识Docker二、Docker架构三、安装Docker四、启用Docker五、Docker的基本命令六、Docker数据卷七、Docker命令小结(仅看命令可跳到此处)一、认识Docker1、Docker解决......
  • ℬ悟透C++┇Puzzle记录
    C++Puzzles★1.有如下代码,问:ptr指向了谁?能通过ptr调用Derived类重写的函数吗(即多态还起作用吗)?dynamic_cast到底是什么作用?ptr2与ptr性质是一样的吗?Derived*derived=ne......
  • 大数科学计算器 C++
    大数计算器目录大数计算器优点实现思路自定义类MyNum类★★★Method类具体函数实现MyNum类的函数构造&三大函数数学函数转换函数运算符重载显示函数Method类的函数构......
  • 小程序的基础知识
    小程序和普通的网页开发有什么不同?1.网页的运行环境是浏览器,而小程序的运行环境在微信中2.小程序不能调用BOM和DOM的API,他有自己的API3.开发方式不同,网页开发是浏览器+......
  • [java-基础]-Java构造方法的执行顺序
    (一)Java构造方法的执行顺序1.如果类里边没有定义任何构造方法,则系统将添加一个默认的无参构造方法。ClassThisClass{}默认隐式添加无参的构造方法,相当于ClassThi......
  • [Java]基础篇-对象存储OSS
    商城品牌管理新建品牌管理菜单逆向生成的vue代码复制到vscode工作空间brand.vue页面代码brand-add-or-update.vue添加修改组件代码启动项目:对显示状态进行优化......
  • [java基础]-反射
    (一)反射:框架设计的灵魂1.反射介绍*框架:半成品软件。可以在框架的基础上进行软件开发,简化编码*反射:将类的各个组成部分封装为其他对象,这就是反射机制 *好处: 1.可以......