C++


一、基础语法

1.0 hello again

#include <iostream>          
using namespace std;      //使用标准命名空间
int main()                //主函数
{                         //语句块中定义的变量具有局部定义域
    cout << "Hello, world!" << endl;   //输出流
    //endl 插入换行符,并刷新流
    
    system("pause");      //程序不会自动跳出
    return 0;
}

C++语言特性

  • 静态类型的、编译式的
  • 支持过程化编程、面向对象编程、泛型编程。

1.1 基本语法

标识符:①以字母或_开始 ②由数字、字母、_组成 ③不能是关键字或操作符

空白符:空格、制表符、换行符、回车符和注释的总称

调试:设置断点,F5,,F11 逐语句,F10 逐过程

注释://单行注释,/*...*/多行注释

1.2 数据类型

signed unsigned

变量类型: bool char short int long long long float double
字节数 1 1 2 4 4 8 4 8

.
显式数据类型转换:
int(z) (int)z static_cast<int>(z) 完全等价
.

转义字符: \n \t \v \b \r \\
含义 换行 水平制表符 垂直制表符 退格 回车 字符\

1.3 运算符

  • 常量
    • 整数常量
      • 八进制:以0开头
      • 十六进制:以0x开头
    • 浮点型常量(近似存储)
    • 字符串常量
      • C风格字符串常量:以\0结尾的字符序列
      • string类对象
    • 符号常量(eg. const float pi(3.14)
  • 运算符
    • 算术运算符:+ - * / % ++ --
    • 关系运算符:== != > >= < <=
    • 逻辑运算符:&&(短路特性) || !
    • 位运算符:& | ^ << >> ~(二进制补码运算符)
    • 赋值运算符:=及其衍生
//逗号运算符,先计算表达式1,再计算表达式2,结果为表达式2的值
a = 3*5, a*4 //最终结果为60

//sizeof运算符
//Condition ? x : Y 条件运算符
//指针运算符 & *

1.4 输入输出

  • 程序建立一个流对象
  • 指定这个流对象与某个文件对象建立连接
  • 程序操作流对象,通过文件系统产生作用

<iostream> 定义了cin, cout, cerr, clog
<fstream> 文件处理

getline(cin,a); //输入整行字符串,第三个参数指定分隔符,默认为换行

1.5 结构控制

//判断
if-else

switch(day){
  case 0: cout<<"Sunday"<<endl; break;
  //break防止继续执行
  ...
  default:
    ...
    break;
}

//循环
while(){}
do{}while();
for(;;){}

//break, continue, goto

注意:如果要在case里面定义变量,需要用括号 { } 括起来,不然会出错

1.6 字符串

  • C 风格字符串
    • 常字符指针,const char *p = str ,指向首地址
  • string类
string s1;  //构造,默认建立一个长度为0的串
string s2="abc";
string s3=s2; //复制构造函数

//字典序比较,下标访问字符

1.7 other

一、一些类型

//类型别名
typedef 已有类型名 新类型名
using 新类型名 已有类型名

//枚举类型
enum 类型名 {变量值列表};
//枚举元素是常量,枚举元素有默认值

//auto,根据初始值自动推断变量类型

decltype(i) j = 2 //表示j以2为初始值,类型与i一致

二、标识符的作用域与可见性

#include<iostream>
using namespace std;

int i;//全局变量,文件作用域
int main(){
    i=5;
    {
        int i;//局部变量,局部作用域({}中),屏蔽外部
        i=7;
        cout<<"i="<< i <<endl;//输出7
    }
    cout<<"i="<< i <<endl;//输出5
    return 0;
}

关于可见性:

  • 如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见
  • 对于两个嵌套的作用域,如果内层中声明了外层作用域同名的标识符,则外层的那个表示符在内层中不可见

三、变量的生存期与可见性

#include<iostream>
using namespace std;
int i=1;//i为全局变量,具有静态生存期
void other(){
    static int a=2;
    static int b;
    //a,b为静态局部变量,具有全局寿命,局部可见
    //只第一次进入函数时被初始化
    int c=10;
    //c为局部变量,具有动态生存期
    //每次进入函数时都初始化
}

二、面向对象

2.1 概述

如何形成类:抽象出同一类事物的共同属性和行为,形成类。

类与对象的关系:类型与实例的关系,犹如模具与铸件之间的关系。

OOP思想:封装、继承、多态

class Clock
{
private: //私有成员
    int hour = 0; //类内初始值

public: //共有成员,外部接口
    //通过初始化列表构造函数:
    // Triangle(int a, int b, int c) : sa(a), sb(b), sc(c) {}; 

    //一般通用的类都会提供一个默认构造函数
    //Clock();
    void show();

protected: //保护型成员
}

Clock::show(){   //成员函数的实现

}

Clock t; //对象定义

t.show() //调用

如何定义类的成员函数:

(1)标准形式,头文件中声明,cpp文件中实现
(2)隐式内联,直接在头文件中定义且实现
(3)现实内联,头文件中声明内联函数,cpp文件中实现

2.2 构造函数

构造函数的特征:

  1. 构造函数必须与类重名
  2. 构造函数可以有任意类型的参数
  3. 不能有返回值,不能定义返回类型
  4. 构造函数在 建立对象时自动执行
// complex.h
class Complex{
private:
    double real;
    double imag;
public:
    Complex(double r, double i);
};
//complex.cpp
#include "complex.h"

Complex::Complex(double r, double i){
    real = r;
    imag = i;
}

一个类中可以没有用户定义的构造函数,此时编译器会自动为用户提供一个 默认构造函数 (无参构造函数)。

  1. 参数表为空的构造函数
  2. 全部参数都有默认值的构造函数

但两个默认构造函数在类中同时出现时,会产生编译错误。

class ...{
private:
public:
    类名(形参);//构造函数
    类名(const 类名 &对象名);//复制构造函数
}
//对象数组的构造

// 1.缺省构造函数,无参数构造函数
classname foo[20];

// 2.构造函数带一个参数,且不是默认参数的情况
classname foo("parm");

// 3.构造函数的参数有多个时,为每个元素显式调用其构造函数
classname foo[2]={
    fo(...),
    fo(...)
};

//

拷贝构造函数:

  • 每个类都有拷贝构造函数,如果没有定义,则使用默认的
  • 使用默认的拷贝构造函数,实现的功能也是内存的直接填充
Class Point{
private:
    int x,y;
public:
    Point(int a, int b): x(a),y(b){}
    Point(const Point &p){             //拷贝构造函数,,,构造
        x=2*p.x;
        y=2*p.y;
    }
}

Point p1(30,40);
Point p2(p1); //显式调用拷贝构造函数复制对象
Point p3=p2; //通过赋值来调用拷贝构造函数,隐式调用
Point p4(0,0);
p4=p1; //这里是赋值,不是复制。

Point func2(){
    Point p4(10,20);
    return p4;
}
p2=func2() //传出来的是对象拷贝构造后的,20,40

下列情况中,不会调用拷贝构造函数的是( B )。

A) 用一个对象去初始化同一类的另一个新对象时
B) 将类的一个对象赋值给该类的另一个对象时
C) 函数的形参是类的对象,调用函数进行形参和实参结合时
D) 函数的返回值是类的对象,函数执行返回调用时

2.3 析构函数

  1. 一个类中可以有多个构造函数,只能有一个析构函数
  2. 如果程序中未声明,编译器自动产生一个默认的析构函数。
  3. 在对象的生存期结束的时候,系统自动调用析构函数。
  4. 析构函数没有参数,没有返回类型
class ...{
private:
public:
    类名(形参);//构造函数
    ~类名();//析构函数
}

classname::~classname(){}//析构函数

2.4 静态成员&函数

静态成员

//在Student中 声明 一个静态的学生总数变量
static int stu_cnt;
//构造函数中添加
stu_cnt++;
//在任意一个对象中修改该值,会影响到该类的所有对象
//静态成员在编译时初始化,是一块共享的内存空间


//在类的外部,main的外部,初始化静态成员变量
int Student::stu_cnt=0;

//静态成员函数不能声明为虚函数

//静态数据成员是类的所有对象所共有的
//静态数据成员能受private控制符的作用,可以直接用类名调用
//静态数据成员在类外初始化

静态成员函数

  1. 静态成员函数仅用来操作静态成员变量
  2. 静态成员函数是类的“全局”函数,因此没有this指针。在静态成员函数中,一般无法直接访问类的其他变量
  3. 静态成员函数仅用来操作静态成员变量
  4. 在任何对象被构造之前,静态成员变量和静态成员函数就已经可以访问。

调用:类名::静态函数名();

static函数 属于类,不属于对象(对象里不分配内存),this指针是对象的指针。所以在cpp静态成员函数里,不能直接使用this指针。

2.5 友元&运算符重载

友元函数,在类的外部,访问类的私有成员

//友元函数在类中声明
friend float dist(Point &a, Point &b);

//友元函数访问私有成员变量,但是不能直接在定义友元函数时调用私有成员变量,只能通过引用类对象的成员参数方式调用
float dist(Point&a, Point &b){
    double x = a.x - b.x;
    double y = a.y - b.y;
    return sqrt(x*x + y*y);
}

//友元函数不是类的成员函数
//现行的C++中,通常用于运算符重载函数。

运算符重载

(1)不能重载的运算符:. .* :: sizeof ?:
(2)不是C++的运算符:**,不可重载
(3)运算符操作目数不变,优先级不变,结合特性不变。
(4)运算符重载,其操作数至少有一个是类的对象。

考察:自增,自减重载

2.6 派生&继承⭐

class car{
    //基类中属性与方法
    int f(){ k = 0 };
}
class bus: public car{
    //出现在基类中的属性与方法,不用再次声明和实现
    //只写派生类中需要新添的属性和方法
    int f(){ k = 1 };   //派生类中重新定义f()
    int g(){car::f();} //调用基类方法
}
(2)访问控制和继承
  • 共有继承
    • 基类的 publicprotected 成员,访问属性在派生类中保持不变
    • 基类的 private 成员,不可以直接访问
    • 派生类中的成员函数:可以访问基类中的 publicprotected 成员,不能访问基类中的 private 成员
    • 通过派生类的对象,只能访问 public 成员
  • 私有继承
    • 基类的 publicprotected 成员,都以 private 身份出现在派生类中
    • 基类的 private 成员,不可以直接访问
    • 派生类中的成员函数:可以访问基类中的 publicprotected 成员,不能访问基类中的 private 成员
    • 通过派生类的对象,不能直接访问从基类继承的任何成员
  • 保护继承
    • 基类的 publicprotected 成员,都以 protected 身份出现在派生类中
    • 基类的 private 成员,不可以直接访问
    • 派生类中的成员函数:可以访问基类中的 publicprotected 成员,不能访问基类中的 private 成员
    • 通过派生类的对象,不能直接访问从基类继承的任何成员

protected成员的特点与作用

对建立其所在的类对象的模块来说,用 private 成员的性质;(即类外无法访问)
对其派生类来说,同 public 成员的性质

一个派生类继承基类方法时,以下情况除外
(1)基类的构造函数、析构函数、拷贝构造函数
(2)基类的重载运算符
(3)基类的友元函数

  • 派生类调整基类成员的方法
    • 同名覆盖
    • 访问声明
      • 在派生类的public段中声明父亲类中某个函数,令其属性为public
//虚继承
//祖先类的成员只构造一次
class Derived: virtual public Base{...}; 
(4)派生类的类型转换
class Base{...}
class Derived: public Base{...}

Base b;
Derived d;

b = d; //用派生类对象给基类对象赋值
Base &br = d; //定义一个基类应用,用派生类初始化
Base *bp = &d; //定义一个基类指针,指向派生类对象
(5)派生类的构造
Teacher::Teacher(string n, int a, int s, int i, string sc, string c)
    :Person(n, a, s)              // 将前3个参数传入继承的Person属性
{
    id = i;
    sch = sc;
    course = c;
}

2.7 多态&虚函数⭐

父亲类定义函数接口,孩子类负责实现该接口。当父亲类需要调用该接口时,能够自动执行对应孩子实现的代码

(1)虚函数

通过虚函数实现运行时多态,虚函数是动态绑定的函数。

//虚函数是属于对象的,不属于类

//一般成员函数、析构函数可以是虚函数,构造函数不能是虚函数

#include<iostream>
using namespace std;
class Base1{
public:
    void display() const{
        cout << "Base1::display()" << endl;
    }
};
class Base2:public Base1{
public:
    void display() const{
        cout << "Base2::display()" << endl;
    }
};
class Derived:public Base2{
public:
    void display() const{
        cout << "Derived::display()" << endl;
    }
};

void fun(Base1 *ptr){
    ptr->display();
}

int main(){
    Base1 base1;
    Base2 base2;
    Derived derived;
    fun(&base1);          //运行时使用指针访问继承中的同名成员会出错
    fun(&base2);          //全部输出为Base1::display()
    fun(&derived);        //使用指针,出错
}


//可通过虚函数实现运行时多态,进行改进
virtual void display() const;
(2)虚析构函数

如果允许通过基类指针去调用对象的析构函数(通过delete这样是正常的),就要让基类的析构函数为虚函数。

Base *p = new Derived();
//只在派生类中构造析构时,动态内存
//这样的调用,在析构时内存泄漏

//解决方法:将析构函数变成虚析构(两个)
//根据虚函数派生来的对象同原型函数,可不写virtual
(3)纯虚函数
virtual 函数原型 函数名(参数表) = 0;

抽象类,(带有纯虚函数的类),无法用于定义对象


override,检查是否覆盖

final,不允许被继承或覆盖

2.8 other

//类的组合
//在一个类的构造中嵌入另一个类的对象,构造时都要构造
//先构造被组合的


//对象指针
Ccube* pcube;  //定义指针时,不会调用构造函数

//定义类时,类的方法编译后的二进制码放入内存
//定义类的对象时,对象的内存空间存放属性数据


//在每个成员函数执行的时候,隐形地传进去一个指向调用者对象自身的 this 指针
a.disp()  -> a.disp(this) -> a.disp(&a)


//常对象
const class_name foo();
class_name const foo();
//此后foo对象在其生存周期内不可更改

//const数据成员只能透过初始化列表在构造的时候进行初始化。一旦构造不可更改
//常成员
//一个类中定义的const函数,专门用来处理这个类的const对象发起的函数调用
class Date{
private:
    int year, month, day;
public:
    Date(int y, int m, int d):year(y), monnth(m), day(d){}
    void showdate(){
        cout<<y<<endl;
    }
    void showdate() const{
        cout<<"const"<<endl;
    }
}
int main(){
    Date date1(1990,12,1);
    date1.showdate();

    Date const date2(1998,1,1);
    date2.showdate(); //调用const函数

    return 0;
}

//const函数能访问普通属性成员,但不能修改值

三、高级语法

3.1 指针⭐

(1)指针说明
  1. 有效指针。指向本程序内存范围内有效的地址
  2. NULL指针。存在但不指向任何地方
  3. 野指针。指向未知内存的指针,如:char *p
(2)const 与指针
const char *p = "city"; //指向字符型常量的指针,它指向的值不能修改
char * const p = "city"; //指向字符型变量的常指针,不能指向别的变量,但指向变量的值可以修改
const char const * p = "city";  //指向字符型常量的常指针。
(3)指向对象的指针
Point a(4,5);
Point *p1 = &a;
cout << p1->getX() << endl;   //指针访问对象成员

this 指针,隐含于类的每一个非静态成员函数中

3.2 引用

  1. 引用(&)是标识符的别名
  2. 定义时需初始化,不能定义空引用
  3. 区分取址、引用
  4. 引用不可更改
  5. 引用必须有类型,且与原类型相同
  6. 引用的引用仍然是引用
int &j = i;
int *p = &j;
-->int *p = &i;

//引用作为函数参数(声明中),说明参数是对变量的引用
//引用还可以作为函数返回值

3.3 内存操作

动态内存(基础):

int* p;                 //指针定义
p = new int[10];        //分配空间,大小可以确定、也可以不确定
delete [] p;            //释放空间

p = NULL

二维动态数组:

char** pchar = new char*[m];
for(int i = 0; i<m; i++)
    pchar[i] = new char[n];

for(int i = 0; i<m; i++)      //释放空间
    delete [] pchar[i];       //不然会导致内存泄漏
delete [] pchar;

3.4 函数

在C++中,不再单纯依靠函数名字来确定函数,因此对函数的声明要求更加严格。

以下为合法的函数声明:

  • int Area(int x, int y);
  • int Area(int, int);
(2)内联函数
//内联函数
//减少开销,不会为函数单独开辟区域编译
//不能有循环语句和switch语句
//对内联函数不能进行异常接口声明
//编译器可能优化,,导致用得比较少
inline int max(int a, int b){
    return a>=b ? a : b;
}
(3)默认参数
//默认参数,也叫缺省参数
//默认参数必须放在没有默认值参数的后面
int average(int x, int y, float alpha = 1.0)
(4)函数重载
//函数重载
//允许多个函数使用同样的函数名
//不要将功能不同的函数声明为重载函数
bool show(int a, char *p);     //原始函数
bool show(float a, char *p);    //参数类型不同
bool show(int a, char *p, float c);     //参数数量不同

int add(int x, int y);
float add(float x, float y);

//编译器不以形参名区分
//编译器不以返回值来区分

3.5 结构体

struct name{
    共有成员
protected:
    保护型成员
private:
    私有成员
}

//cpp中结构体是一个特殊的类
//结构体中可以有数据成员和函数成员
//一定情况下可如下初始化:
Student stu={201911,"yasuo",19};

3.6 函数模板

提高代码的复用性

template<typename T>
T abs(T x) {
    return x < 0? -x : x;
}
//自动推导其变量类型

3.7 other

extern 用于全局变量、函数

#if #elif #else #endif 条件编译语句

C++ 文件和流,菜鸟教程

C++ 异常处理,菜鸟教程

四、实例

4.1 三角形类

#include<iostream>
#include<string>
#include<cmath>
using namespace std;

class Triangle {
private:
    int sa, sb, sc;
public:
    // Triangle(int a, int b, int c) : sa(a), sb(b), sc(c) {}; 通过初始化列表构造函数
    Triangle(int a, int b, int c) {                         //普通构造函数
        sa = a;
        sb = b;
        sc = c;
    }

    bool ifcan() {
        if (!((sa < 0 || sb < 0 || sc < 0) || (sa + sb < sc) || (sa + sc < sb) || (sb + sc < sa)))
            return true;
        else
            return false;
    }

    double GetPerimeter(){
        return  sa + sb + sc;
    }

    double GetArea(){
        double p = GetPerimeter() / 2;
        p = p *(p - sa) * (p - sb) * (p - sc);
        return sqrt(p);
    }

    string classify() {
        int flag;
        if (sc >= sa && sc >= sb)
            flag = sc*sc - sa*sa - sb*sb;
        else if (sa >= sb && sa >= sc)
            flag = sa*sa - sc*sc - sb*sb;
        else
            flag = sb*sb - sa*sa - sc*sc;
        if (!flag)
            return "right triangle";
        else if (flag > 0)
            return "obtuse triangle";
        else
            return "acute triangle";
    }

    ~Triangle();   //析构函数声明
};
Triangle::~Triangle(void) {
    cout << "triangle is being deleted" << endl;
}


int main() {
    int a, b, c;
    cout << "input 3 numbers:" << endl;
    cin >> a >> b >> c;
    Triangle t(a,b,c);

    if (t.ifcan())
        cout << "can form a triangle." << endl;
    else
        cout << "can't form a triangle." << endl;

    cout << "perimeter: " << t.GetPerimeter() << endl;
    cout << "area: " << t.GetArea() << endl;
    cout << "category: "<< t.classify() << endl;
    system("pause");
    return 0;
}

4.2 两数之和

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> hashtable;
        for(int i = 0; i<nums.size(); i++){
            auto it = hashtable.find(target - nums[i]);
            if (it != hashtable.end()){
                return {it->second, i};
            }
            hashtable[nums[i]] = i;
        }
        return {};
    }
};

other

参考资料

  • 学校课程 30%
  • C++语言程序设计(学堂在线) 20%
  • C++ Primer 5th
  • 菜鸟教程

相关文章:


关于考试:

  • 类写完,要加括号
  • 多态的含义
  • 设计模式,3抽1

文章作者: ╯晓~
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ╯晓~ !
评论
  目录