C++基础知识总结(详解)

前言

文章篇幅较大,内容充实
请在阅读的过程中保持一个批判的态度

C++基础

C++头文件

C++有自己的头文件,例如iostream 没有.h
也可以用C的头文件 例如stdio.h 有.h
或者将C文件C++化 例如cstdio 前面加c没有.h

名字空间

划分逻辑单元。避免名字冲突

创建名字空间

::作用域限定符

表示::后面的内容属于::前面。翻译成中文就是 的
如果::前面没有内容表示全局


使用:

名字空间合并

有时候一个名字空间太冗余或者其他原因需要分开实现在不同地方

这里不是覆盖而是合并,将同名名字空间合并在一起

声明和定义分开

名字空间嵌套


名字空间别名

名字空间指令

using namespace 名字空间名
使用后该名字空间对于当前作用域可见,可以不再使用作用域限定符,一旦使用,不可再隐藏。

std名字空间全局可见。std标准库定义名字空间

  • 若不同名字空间,需要注意二义性,因为编译器有选择困难症,此时仍然需要使用作用域限定符来明确作用域

C++结构体、联合、枚举

C++结构体

声明或定义结构体变量,可以省略struct
内部可以定义函数

C++ 联合

声明或定义联合变量,可以省略union
支持匿名联合

C++枚举

声明或定义枚举变量,可以省略enum
独立类型和整型不能隐式相互转换

Demo:实现不同银行账户存取。

  • 工商银行(ICBC)原本余额(balance) 2000元,存(save)3000,取(draw)2000之后显示(show)余额
  • 农业银行(ABC)原本余额(balance) 4000元,取(draw)1500,存(save) 800之后显示(show)余额
namespace ICBC
{
   
	int balance = 2000;
	void save(int money)
	{
   
		cout << "工商银行存入:" << money << endl;
		balance += money;
	}
	void draw(int money)
	{
   
		cout << "工商银行取出:" << money << endl;
		balance -= money;
	}
	void show()
	{
   
		cout << "工商银行余额:" << balance << endl;
	}
}
namespace ABC
{
   
	int balance = 4000;
	void save(int money)
	{
   
		cout << "农业银行存入:" << money << endl;
		balance += money;
	}
	void draw(int money)
	{
   
		cout << "农业银行取出:" << money << endl;
		balance -= money;
	}
	void show()
	{
   
		cout << "农业银行余额:" << balance << endl;
	}
}
int main(){
   
	ICBC::save(3000);
	ICBC::draw(2000);
	ICBC::show();
	cout << "\n";
	ABC::draw(1500);
	ABC::save(800);
	ABC::show();
	return 0;
}

bool类型

  • true表示真 单字节整数1
  • false表示假 单字节整数0

任何基本类型都可以隐式转换为布尔类型
非0即真,0即假

boolalpha bool类型使用字符输出
noboolalpha bool关闭字符输出,数值输出


内联

  • 用函数已被编译好的二进制代码,替换对该函数的调用指令。提高效率,避免函数调用开销。
  • 使用inline关键字期望该函数被优化为内联,是否内联由编译器决定
  • 内联会使可执行文件内存变大,只有频繁调用的简单函数适合内联。复杂函数和递归函数都不适合内联。

函数重载

同一作用域中,函数名相同,参数表不同的函数。不同作用域同名函数遵循标识符隐藏原则(临近隐藏原则)。
重载和返回值和参数名没有关系

调用时会根据参数类型自动选择类型一致的函数

重载和返回类型无关

函数作用域不是定义决定的,是声明决定的。

重载是编译器通过换名实现,
在linux下 用gcc -c 获取.o 使用nm .o文件查看
在windows下查看obj文件,或者不定义函数,只声明和使用
通过extern “C”可以要求C++编译器按照C方式处理函数接口

缺省参数和哑元

为函数指定缺省值,调用时若未指定实参,则对应的形参取缺省值。

缺省参数的特点

  1. 最好在函数声明中指定。可以利用声明改缺省值。
  2. 禁止在声明和定义同时指定缺省参数。可能不一致,编译器禁止
  3. 缺省参数只能在最后,即你某个参数指定为缺省参数,后面所有参数都要有缺省值
  4. 不要因为是用缺省参数导致重载歧义

只指定类型而不指定名称的函数参数,叫做哑元。
使用哑元1.兼容之前版本。二.形成函数重载

引用

引用(reference)是c++对c语言的重要扩充。
引用就是某一变量(内存)的一个别名,对引用的操作与对变量直接操作完全一样。其格式为:
类型 &引用变量名 = 已定义过的变量名。

&符号:跟在类型后是引用,没有类型是取地址
*符号:跟在类型后是指针,没有类型是解引用

诸葛亮 诸葛孔明 卧龙 蜀国丞相

引用的特点

  • 一个变量可取多个别名。
  • 引用必须初始化,不能为空。
  • 引用只能在初始化的时候引用一次 ,不能更改为转而引用其他变量。

const引用

引用变量和被引用的变量虽然是同一个变量,但是可以被不同修饰符修饰

引用做参数

  • 节省空间+提高效率
  • 值传递,形参生成局部临时变量接收实参的值。
  • 引用传递,形参是实参的别名
  • 指针传递,传入实参的地址,指针通过地址访问修改值


引用做函数返回值

当引用做函数的返回值时: 函数可以放在赋值语句的左边(可以当左值)

动态分配+内存池

使用标准C malloc/calloc/realloc/free

使用new delete

在分配内存的同时初始化

数组方式new的需要以数组方式delete

在C11标准中可以初始化数据

内存池

预先分配好,放到进程空间的内存块,用户申请与释放内存其实都是在进程内进行,遇到小对象时就是基于内存池的。只有当内存池空间不够时,才会再从系统找一块很大的内存

引用和指针的区别和联系

两者都是地址的概念
指针指向一块内存,其内容为所指内存的地址;
引用是某块儿内存的别名。

  1. 指针是一个实体,而引用仅是个别名;
  2. 引用使用时无需解引用(*),指针需要解引用;
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  4. 引用没有 const,指针有 const;const修饰的指针不可变;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
  7. 指针和引用的自增(++)运算意义不一样;
    8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

Demo: 使用引用和指针实现加法计算


Demo:new 和delete实现二维数组的动态申请内存

用这个方法来创建二维数组,比较直观、易用,但它最大的限制在于:你必须在编译时确定b的大小。

低一级的数组是分开创建的,所以整个二维数组的内存不连续——类似‘array2D[i * width + j]’这样的访问就不要使用了,容易造成访问越界。

C++面向对象

为什么使用面向对象

  • 适合开发大型软件
  • 提升效率,降低成本
  • 已有现成的设计模式和应用框架,拿来就能用

什么是对象

万物皆对象
底层对象能够从高层对象继承一些属性和行为。
例如小狗继承动物的一些属性和行为,动物继承生物的一些属性和行为。
属性和行为可以完整的描述对象。属性是状态,特征。行为是能做什么。
例如认识陌生人,先关注性别,外貌,再交谈
面向对象三大特性:继承,封装和多态

类和对象

拥有相同属性行为的一组对象统一描述成一个类
人类:男人,女人,泰国人妖 等等对象
属性:身高,体重,年龄,三围 等等
行为:吃饭,睡觉,打豆豆,么么哒 等等
类是对对象的抽象,对象是类的具体(实例)化

类的定义

访问类型限定符

  1. public 共有成员
    谁都可以访问(使用/调用)
  2. protected保护成员
    只有自己和子类可以访问
  3. private 私有成员
    只有自己(类内部)可以访问
  4. 成员访问
    访问限定符仅作用于类,因此同一个类的不同对象,可以相互访问非共有部分。
  5. 类和结构体区别
    在C++中,类和结构没有本质区别,唯一不同在于
    类的默认访问控制属性为private
    结构的默认访问控制属性为public
  6. 封装
    对不同成员的控制访问属性加以区分,体现了C++作为面向对象语言的封装特性
class People
{
   
public:
	char name[20];
	int  age;
	int  weight;

	void eat(char* food)
	{
   
		cout << "我吃了" << food << endl;
	}
	void play(char* game)
	{
   
		cout << "我玩了" << game << endl;
	}
};

创建对象

在栈中创建对象

  • 在栈中创建一个对象
    格式: 类名 对象名
    People people;
  • 在栈中创建多个对象
    类名 对象数组名[元素个数]

在堆中创建对象

  • 在堆中创建单个对象
    类名 *指针名 = new 类名
    People *sirius = new People;
  • 在堆中创建多个对象
    类名 *对象数组指针名 = new 类名[元素个数]

String类


Demo: 练习类的使用

狗类

有一个狗类,属性有姓名,犬龄,品种,毛色(不可以外部直接设置)。行为有进食,奔跑,睡觉。
请分别在堆区和栈区实例化一个对象。依次进食,奔跑,睡觉

调用

对象数组

创建一个对象数组,存放4个学生(学号,成绩)(学号和成绩不可外部直接设置,且设置学号时不可重复),设计一个函数max,找出这4个学生成绩最高的,并输出学号。

调用
第二种方式

在这里插入图片描述

类的构造函数

  1. 为什么使用构造函数
    变量有初始化,对于对象,也需要对成员变量赋初值,分配资源(动态内存分配),设置对象的初始状态。
  2. 什么是构造函数
    特殊的成员函数,函数名与类名相同,并且没有返回类型/返回值
  3. 构造函数用在哪里
    在创建对象时自动被调用,用户不能调用,仅被调用一次
    对象创建过程 为对象分配内存空间
    调用构造函数(基类部分,成员变量,构造代码)
  4. 构造函数怎么用
    • 默认(缺省)构造函数
      • 如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。
        例如作业中的Bank类,没有定义构造函数。
        Bank ICBC;调用了默认构造函数
  • 构造函数的重载
    和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。
    构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
    一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。

构造函数为什么要放在public
在外部实例化对象时自动调用构造函数,如果是其他访问方式,没办法调用构造函数,也就不能实例化对象。
(特例,单例模式等。)

参数初始化表

简化成员变量初始化。仅仅只是为了书写方便,没有效率上的提升


注意,参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关

析构函数

  1. 为什么使用析构函数
    创建对象时系统会自动调用构造函数进行初始化工作,对应的,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。

  2. 什么是析构函数
    析构函数也是一种特殊的成员函数,没有返回值,
    构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。

  3. 析构函数用在哪里
    在销毁对象时自动执行。不能显示调用。

  4. 析构函数怎么用
    析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。
    如果用户没有定义,编译器会自动生成一个默认的析构函数。这个析构函数的函数体是空的,也没有形参,也不执行任何操作。

this指针

this 是 C++ 中的一个关键字,也是一个 const 指针。指向当前对象,通过它可以访问当前对象的所有成员。
用->来访问成员变量或成员函数。

成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。

类中const成员

  1. const成员变量
    必须使用初始化参数列表,初始化后不能修改
  2. const成员函数
    const成员函数不能修改基本成员变量。不能调用非const成员函数
  3. const对象
    对象中的数据成员不允许被改变。
    常对象只能调用常成员函数

思考一下代码的输出,注意指针和对象

#include<iostream>
using namespace std;
class Test{
   
    char x;
public:
	Test(char c='A')
	{
   
		cout << c;
	}
};
int main() {
   
    Test p1, *p2; // 只实例化了p1 p2只是一个指针
    p2 = new Test('B'); 
    delete p2; 
    return 0;
}

总结

  1. 构造函数,初始化成员变量和分配资源。实例化对象的时候自动调用。
  2. 没有返回值,函数名和类名相同。
    用户不定义编译器提供无参空函数体的默认构造函数
  3. 用户可以重载构造函数。或者重新定义无参构造函数。一旦重载或者重定义,原来的默认构造函数将不提供
  4. 代码中一旦出现构造函数,默认构造函数不提供
  5. 析构函数,清理资源,对象销毁时自动调用。无参,不能重载。只可以重新实现。
    代码中一旦出现析构函数,默认析构函数不提供

拷贝构造函数(复制构造函数)

一种特殊的构造函数,从相同类型的对象构造。

同构造函数一样,如果用户不提供拷贝构造函数,编译器提供默认拷贝构造函数。
注意:因为拷贝构造函数属于构造函数,有的编译器会在用户提供构造函数的时候也不再提供拷贝构造。

默认拷贝构造函数原型

类名(const 类名& 变量名);

浅拷贝

复制地址

浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针。

深拷贝

复制内存

深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的。拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。

何时需要自定义拷贝构造

  • 是否开启新的内存地址
  • 是否影响内存地址的引用计数

如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。



该代码报错:报错原因是n1,n2,n3的m_pAge共用一块内存,delete n3的时候,内存已经释放,离开作用域调用析构时,m_pAge成为野指针。

能成功调用三次析构,且每个对象m_pAge地址不一样

类static成员

static成员变量

  • 静态成员在类的所有对象中是公有的
  • 内存是程序运行时分配
  • 使用前必须初始化且只能初始化一次
  • 初始化不能在类定义中,通过作用域限定符初始化

类型 类名::变量名=值;

- 静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。
  • 优点
    static成员的名字是在类的作用域中,因此可以避免与其它类成员或全局对象名字冲突。
    可以实施封装,static成员可以是私有的,而全局对象不可以。
    阅读程序容易看出static成员与某个类相关联,这种可见性可以清晰地反映程序员的意图。

static成员函数

  • 不再属于对象,不需要通过对象访问
  • static成员函数没有this指针,所以静态成员函数不可以访问非静态成员。只能访问静态成员(静态成员和静态变量)
  • 非静态成员函数可以访问静态成员
using namespace std;

class test2
{
   
public:
    test2(int num) : y(num){
   }
    ~test2(){
   }//( 构造 析构 )函数

    static void testStaticFun()//静态成员函数
    {
   
      cout << "y = " << y << endl; //Error:静态成员函数不能访问非静态成员
    }

    void testFun()//非静态成员函数
    {
   
        cout << "x = " << x << endl; 
    }
private:
    static int x;//静态成员变量的引用性说明
    int y;
};

int test2::x = 10;//静态成员变量的定义性说明

int main(void)
{
   
    test2 t(100);
    t.testFun();

    return 0;
}

/*
*	静态成员属于类而不属于对象,生命周期进程级
*	相当于全局变量和全局函数,只是多了类作用域和访问控制属性限制
*	静态成员依然后类的作用域和访问控制限定符约束
*	
*	静态成员变量的定义和初始化只能在类外部,不能在构造函数
*	静态成员变量为该类所有对象共享
*	访问静态成员可以通过对象也可以直接通过类
*	
*	静态成员函数没有this指针,没有常属性
*	静态成员函数只能访问静态成员  (没有this指针)
*/
class Account
{
   
public:
	Account(string const&name, int no, double balance) : m_name(name), m_no(no), m_balance(balance)
	{
   
	}

	void save(double money)
	{
   
		m_balance += money;
	}

	void draw(double money)
	{
   
		if (money > m_balance)
			cout << "余额不足" << endl;
		m_balance -= money;
	}

	void query()const
	{
   
		cout << "户名:" << m_name << endl;
		cout << "账号:" << m_no << endl;
		cout << "余额:" << m_balance << endl;
	}

	void settle()
	{
   
		this->m_balance *= (1 + m_rate / 100);
	}

	static void adjust(double rate)  
	{
   
		//this->m_name; 没有this 不能访问 静态成员函数只能访问静态成员
		if (rate>0)
		{
   
			m_rate = rate;
		}
	}

private:
	string m_name;
	int m_no;
	double m_balance;
	static double m_rate;//类里声明
};
//定义静态成员变量
/*静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义
*定义是给静态成员变量分配内存。
*只能在定义的时候初始化*/
double Account::m_rate =0.2 ;//类外定义 初始化

int main(int argc, char* argv[])
{
   
	Account acc1("小风同学", 101, 4000);
	acc1.draw(2000);
	acc1.query();

	Account acc2("海绵", 102, 40000);
	acc2.save(50000);
	acc2.query();
	cout << "-----------------" << endl;
	cout << sizeof(acc2) << endl;
	acc1.settle();
	acc1.query();
	acc2.settle();
	acc2.query();

	getchar();
	return 0;
}

Demo:实现单例模式

//饿汉式 程序启动就创建 不管你用不用
class single
{
   
private:
	int m_data;
	single(){
   }
	single(int data) :m_data(data){
   }
	single(const single&){
   }
	static single s_instance;

public:
	static single& getinstance()
	{
   
		return s_instance;
	}
};

single single::s_instance(100);


class singleton
{
   
private:
	int m_data;
	singleton(){
   }
	singleton(int data) :m_data(data){
   }
	singleton(const singleton&){
   }
	static singleton* s_instance;

public:
	static singleton& getinstance()
	{
   
		if (!s_instance)
		{
   
			s_instance = new singleton(200);
		}
		
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

√沫影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值