C++学习

C++入门教程
本文详细介绍C++语言的基础知识,包括语言的历史背景、基本语法、数据类型、函数使用、内存管理等内容,并介绍了面向对象编程的基本概念。

C++

目录

文章目录

一 介绍

Bjarne Stroustrup(本贾尼·斯特劳斯特卢普,于1979年在分析Linux系统分布内核流量分析时,希望有一个更加模块化的工具,于是他为C语言增加了类的机制(面向对象),1983年完成了C++的第一个版本。

特点:
  1. C++完全兼容C语言。
  2. 支持面向对象的编程思想(抽象,封装,继承,多态)。
  3. 支持函数,运算符重载。
  4. 支持泛型编程,模板技术。
  5. 支持异常处理机制。
  6. 类型检查更严格。

二 第一个C++程

1.文件扩展名

.cpp .cc .C .cxx

2.头文件

#include
#include <stdio.h>//可以继续使用
#include //建议这样使用 里面删除了一些可能引起冲突的宏

3. using namespace std;//增加了名字空间
4. 输入输出函数
  • cout << 输出数据

  • cin >> 输入数据

  • 不需要对变量取地址,也不需要占位符,因为他们可以自动识别数据类型。

  • scanf/printf/sscanf/sprintf 可以继续使用

注意:

scanf/printf是C标准库函数,cin/cout是C++标准库中的类对象,被定义在std名字空间中。

5. 编译器

g++ file.cpp -> a.out

大多数LInux系统都需要额外添加参数,ubuntu系统安装命令:

  • sudo apt-get updata
  • sudo apt-get install g++

gcc编译器也可以继续使用,需要额外增加参数, gcc -xc++ xx.cpp -lstdc++

6. 练习
1. Hello
#include <iostream>
#include <stdio.h>//可以继续使用
#include <cstdio>//建议这样使用 里面删除了一些可能引起冲突的宏
using namespace std;//增加了名字空间

int main(int argc,char* const argv[])
{
	cout << "Hello World"<<endl;
	return 0;
}
2.求最大,最小,平均
#include <iostream>
using namespace std;

int main(int argc,const char* argv[])
{
	int n,x,max,min;
	double s=0;
	cin >> n;
	cin>>x;
	max=x;
	min=x;
	s = 1.0*x/n;
	for(int i=1;i<n;i++)
	{
		cin >> x;
		if(max < x)
			max = x;
		if(min > x)
			min = x;
		s+= 1.0*x/n;
	}
	cout <<"最大值: "<< max<<"最小值: "<<min<<"平均值: "<<s;
}

三 名字空间

1.为什么需要名字空间

在项目中函数名,全局变量,结构,联合,枚举,宏等,非常有可能命名冲突,而C++之父设计名字空间就是为了解决命名冲突,名字空间可以把这些命名划分到不同逻辑空间里,从而解决命名冲突

2.定义名字空间
namespace name{

定义变量

定义函数

定义类,结构,联合,枚举

}

  • 空间名可以重复,会自动合并
3.使用名字空间

使用空间中的单个标识符, 空间名::标识符,安全

导入空间中所有的标识符,using namespace 空间名,方便依然有冲突的可能

4.名字空间的嵌套

定义: 名字空间的内部可以再定义名字空间

内层标识符可以与外层重名,内层会自动屏蔽外层的标识符。

namespace n1{
	int x =10;
	namespace n2{
		int x = 20;
		namespace n3:{
			int x=30;
		}
	}
}

多层空间使用时需要逐层分解

n1::n2::n3::x

使用多层名字空间时为了精简长度可以取别名
    namespace n123 =n1::n2:n3;  效果等同于 n3
5.匿名空间

默认定义全局的标识符归属到匿名空间中,可以直接使用域限定符::访问匿名空间中的标识符。

#include <iostream>
using namespace std;//增加了名字空间

int x =10;
int main(int argc,char* const argv[])
{
	int x = 20; 
	cout << x <<endl; //局部 			输出20
	cout << ::x<<endl//全局(匿名空间)	输出10
	return 0;
}

四. 数据类型

1.布尔类型

C++中有真正的布尔类型,不需要加头文件,bool true false 可以直接使用

2.结构(struct)联合(union)
  1. 不再需要typedef,在定义结构,联合变量时,可以省略 struct union关键字
  2. 成员可以是函数,成员函数可以直接访问成员变量,不需要 . ->
#include <iostream>
using namespace std;
struct Student	//不需要typedef
{
	char name[20];
	char sex;
	int age;
	void func(void)	//成员函数可以直接访问成员变量
	{
		cout<<"name: " <<name <<endl;
		cout<<"sex: " <<sex<<endl;
		cout<<"age: " << age <<endl;	
	}
};
int main(int argc,const char* argv[])
{
	Student stu = {"小明",'w',22};	// struct 省略
	stu.func();			
}
  1. 有四个隐藏的成员函数(构造,析构,拷贝构造,赋值构造)

  2. 可以对成员的访问权限进行管理。 public/protected/private

  3. 结构还可以被继承

3. 枚举(enum)
  1. 不需要typedef,在定义枚举变量时可以省略enum 关键字
  2. 使用方法与C语言基本一致,不能与整形数据混用
#include <iostream>
using namespace std;

enum Key
{
	up=183,dowm=184,right=185,left=186
};

int main(int argc,const char* argv[])
{
	Key P = 182;//错误: 从类型‘int’到类型‘Key’的转换无效 
														
}
4. void*
  1. 在c语言中void*可以与任意类型指针自动转换(万能指针)
void* p = NULL;
int* p1 = p;
p = p1;
  1. C++中void*不能再直接给其他类型指针变量赋值,只能强制类型转换后才能赋值,但其他类型指针可以直接给void *类型的指针赋值
void* p = NULL;
int* p1 = p;	//error
int* p1 = (int*) p //正确
  1. C++为什么要修改 viod *

为了安全,另外C++可以自动识别类型,所以对万能指针的需求不再那么强烈。

5. 字符串
  1. C++中把字符串封装成了string类,实现在string中,但已经被iostream直接包含,定义在std名字空间中。

  2. 常见的字符串操作不再需要函数,可以使用运算符:

    • = strcpy
    • == strcmp
    • += strcat
  3. 计算字符串成员长度使用 size成员函数

  4. 也可以用与转换成C语言中的char*

    * c_str() const char*
    * data() char*
    

五 函数

1. 函数重载
  1. 什么是函数重载

    • C++中 在同一作用域下,形参列表不同的同名的函数构成重载关系且不会冲突。
    • 调用函数时,编译器会根据实参的数据调用合适的函数
  2. 重载实现的机制

    • C++代码在编译时 函数的参数类型会添加到函数名中,也就是说C++函数在编译时会经历换名的过程,借助这个方式实现了函数的重载。
    • 由于C++和C函数的编译机制不同,所以C++代码不能调用C编译器所编译出的函数。
  3. extern “C” { }

    • 告诉C++编译器按照C语言的机制声 明函数,这样C++的代码就可以调用C编译器 编译出的函数了(C++目标文件与C的目标文件才能合并出可执行文件)
  4. 重载跟作用域

    • 函数的重载关系一定发生在同一作用域下,不同作用下的同名函数构成的是隐藏关系
  5. 重载函数的调用

    当调用重载函数时,编译器会根据实参的数据类型选择合适的重载函数,实参与形参匹配的情况有三种:

    1. 编译器找到实参与形参完全匹配的函数,编译器会生成调用指令
    2. 编译器找到多个匹配函数,但没有一个最佳的,编译器会产生二义错误。
      • 绝大数情况下都编译器都能找到一个最佳的匹配函数,但如果没有,编译器就会进行类型提升,这样备选函数中就可能有多个可调用的版本,然后二义性错误就产生了。
    3. 编译器找不可调用的重载函数,会直接产生错误。
  6. 指针类型也会影响函数的重载

    C++函数的参数如果是指针类型的,编译时就会在函数的末尾添加 Px。(x为指针的类型)

  7. 如果参数是指针或引用,是否加const也会影响函数的重载,PKx。

函数重载 注意:

​ 函数重载是面向对象编程思想的多态(多种形态,根据实参情况对指令作出相应的反应)的体现,具体调用哪个版本的函数是在编译期间就确定了,所以这种也叫编译时多态。

2. 默认参数
  1. 在C++中函数的参数可以设置默认值,在函数调用时,如果调用者没有提供实参,则使用默认的形参。
  2. 如果只有一部分参数设置了默认形参,则设置的默认形参靠右排列。
  3. 如果函数的声明跟定义 分开实现,则只需要在函数的声明时设置默认形参。
  4. 默认形参会影响函数的重载,慎重给重载函数设置默认形参。
3. 内联函数
  1. 普通函数会在调用的位置生成调用(跳转)指令,当执行到调用代码时会跳转到函数所在的代码段位置执行。
  2. 内联函数就是把函数编译好的二进制指令拷贝到调用位置,也就没了跳转和返回的过程。
  3. 内联函数的优点与缺点
    • 与普通函数相比,内联函数的执行速度更快。
    • 大量使用内联会使可执行文件变大,造成冗余(多份)。
  4. 显示内联隐式内联
    • 显示内联 : 在函数的返回值类型前 加 inline(C语言也支持)
    • 隐式内联 : 在结构,类,联合的成员函数会默认优化成内联函数。
    • 是否内联 : 由编译器决定(部分编译器没有实现这个功能)。
  5. 宏函数在调用时,会把函数体直接 替换到调用位置 与内联函数一样,用空间换取时间。
  6. 宏函数内联函数的区别(优缺点)。
  7. 宏函数仅仅是代码替换不是真正的函数,也就不会由参数传递,入栈,出栈,返回值,而且所有的类型都可以使用,但不会进行类型检查,有安全隐患。
  8. 内联函数是真正的函数的调用,函数在调用时会进行参数的类型检查,传参,压栈,出栈,也可以有返回值,也可以重载,设置默认形参,但是这样就不能通用。
  9. 什么样的情况使用于内联函数
    • 由于使用内联函数会使得可执行文件增大,增加内存开销,只有频繁调用且简单的短小的函数适合作为内联函数。
    • 调用比较少且内容庞大复杂的函数,内联后并不能显著提升性能,不足以抵消牺牲空间换来的损失。所以不适合内联。
    • 带有递归特性,和动态绑定的特性的函数,无法实施内联,因此编译器会自动忽略inline关键字。

六 堆内存管理

1. new/delete与malloc/free
  1. new使用方法 :

    • new 类型 例:int* p = new int;
    • new会自动计算类型所需的字节数,然后从堆内存申请相对应的内存块,并放回内存块的首地址(有类型地址)
  2. delete使用方法:

  • delete 地址 例 : delete p;
  • 释放内存中的内存块

注意: new/delete 与malloc/free 不能混用,因为 new/delete除了管理堆内存以外,还有其他的工作要做。

2. 数组的分配与释放
  1. new 类型[n]; n表示数组的长度

  2. delete[] 地址;

  3. 注意: 通过 new[]申请的内存,必须使用delete[] 释放,就是不能混用

3.重复释放
  • delete/delete[] 不能重复释放同一块内存

  • delete/delete[] 释放野指针的后果不确定,但释放空指针是安全的

4.内存申请失败

当内存申请过大,没有满足需要的整块内存时,会抛出异常。

"std::bad_alloc" 
5. 区别
区别new/deletemalloc/free
身份运算符函数
参数类型(自动计算字节数)字节数
返回值带类型的地址字节数
申请失败抛出异常返回NULL
调用函数构造/解析
6.相同点
  1. 都可以管理堆内存
  2. 不能重复释放
  3. 都可以释放空指针

七 强制类型转换

C++中有更安全的强制类型转换

1.静态类型转换(static_cast)
static_cast<目标类型>(源类型)
  • 源类型和目标类型 只要有一个方向可以隐式转换,那么两个方向都可以做静态类型转换,如果不能则报错。
int num = 10;
static_cast<short>(num)
2.动态类型转换(dynamic_cast)
dynamic_cast<目标类型>(源类型)
  • 源类型和目标类型必须同是 引用指针,且目标类型的源类型之间存在继承关系,否则报错
3.去常态类型转换(const_cast)
const_cast<目标类型>(源类型)
  • 源类型和目标类型必须同是 引用指针,且目标类型的源类型只有常属性的区别,否则报错
  • 区别在const有没有。
4.重解释类型转换(reinterpret_cast)
reinterpret_cast<目标类型>(源类型)
  • 源类型和目标类型必须同是 指针+指针,或者 指针+整数,否则报错
5. 设计的初衷

本贾尼·斯特劳斯特卢普:如果让我重新设计,它们会更长。

原因: C++之父不希望程序员使用强制类型转换,如果程序中用到了强制类型转换则说明程序设计的不完善,程序员应该去优化程序的设计,而不应该强制类型转换。

八 引用

1. 什么是引用

引用就是区别名(外号),引用就是用一个新的标识符与另一个标识符的内存建立绑定关系。

#include <iostream>
using namespace std;

void swap(int& x,int& y)
{
	int t=x;
	x=y;
	y=t;
}

int main(int argc,const char* argv[])
{
	int x,y;
	cin>>x>>y;
	swap(x,y);
	cout<<x<<y;
}
2. 引用的基本特性
  1. 引用必须初始化,也就是必须有引用目标。
    • 不存在空的引用,但可能有悬空引用(变量已经销毁,但名还留着)
#include <iostream>
using namespace std;

int main(int argc,const char* argv[])
{
	int* p = new int;
	*p =1234;
	int& x = *p;
   // delete p;      //销毁后不存在了,但名还在
    cout<<p<<" "<<&x;
	cout<<endl;
	cout<<*p<<" "<<x;
 
}
  1. 可以引用无名对象或临时对象,但必须使用常引用。
#include <iostream>
using namespace std;

int main(int argc,const char* argv[])
{
	const int& x =3+7 ;
	cout<<x;
}
  1. 引用不能更换目标
  • 引用一旦定义,初始化完成后就和普通变量一样了,他就代表了引用目标,一旦引用终身不能跟换引用目标(没有这种语法)
  1. 如果引用的目标具有const属性,那么引用也必须带有const。
#include <iostream>
using namespace std;

int main(int argc,const char* argv[])
{
	const int x =7 ;
	//int& t =x;		//将类型为‘int&’的引用初始化为类型为‘const int’的表达式无效
    const int& t =x;
	cout<<t;
}
3 什么时候使用引用
  1. 参数型引用:
    • 使用引用来做为函数的参数,能达到指针的效果(函数之间共享局部变量),但不具备指针的危险性。
    • 函数的实现者有较大主动权,是否使用引用由函数的实现者(写函数的人)说了算。
    • 引用当作函数的参数传参的效率更高,比指针还高,指针至少需要4字节的内存空间来存储变量的地址,而引用只是增加了一条标识符与内存的绑定记录。
  2. 返回值型引用
    • 引用当作函数的返回值,传参的效率更高,但接收者也要使用引用来接,才能提高效率,否者还是内存拷贝。
    • 但不要返回局部变量的引用,否则会造成悬空引用。
4. 引用与指针的区别(重要)
指针引用
身份数据类型取名机制
与目标的关系指向绑定,映射
内存使用4|8字节
危险系数野指针,空指针,越界悬空引用
  • 引用的优缺点: 安全,效率高,但没有管理内存的能力
  • 指针优缺点:无所不能,但是危险,需要额外的内存
指针引用
可以不初始化必须初始化
可以有空指针没有空引用
可以有二三级指针没有多级引用
有指针数组没有引用数组
有数组指针有数组引用
有函数指针有函数引用

九 面向对象编程

面向过程编程:关注于解决问题的方法,步骤

面向对象编程:关注于“谁(类)”能解决问题,解决问题需要的“数据(成员变量)”,以及解决问题需要的技能(成员函数)”。

  1. 抽象:想象出一个能解决问题的对象(观察,想象),分析出解决问题需要的属性(成员变量)行为(成员函数)*。

  2. 封装:把抽象的结果封装成类(数据类型),并设置相应的访问权限。

    • 使用封装的类实例化对象,调用对象的成员函数与成员变量相互作用,达到解决问题的目的。
  3. **继承:**在解决问题之前,先寻找有没有能解决部分问题的类,如果有则把旧的类继承并再次扩展,缩短解决问题的时间,减低解决问题的难度。

  4. **多态:**对象的多种形态,当对象发一条指令,对象会根据自身的情况做出独特响应。

十 类和对象

1.类
  1. 把抽象结果封装成一个数据类型(结构,类)
  2. 类就是一种数据类型
    1. 简单类型:只能表示一个变量,C/C++内建类型
    2. 数组类型:类型相同的多个变量
    3. 结构类型:不同类型的多个变量
    4. 类类型:不同类型的多个变量,函数
2.对象
  • 类这种数据类型创建出实例,相当于结构变量。

十一 类的定义与实例化

1.类的一般形式
class 类名 :继承方式 父亲名;

{

访问限定符;
//	构造函数
    //无参构造
    类名(void{}
	//有参构造
    类名(形参列表)
    {
        xxx = _xxx
        xx = _xx
    }
        
	成员变量;

//	成员函数;
    
	返回值 函数名(形参列表)
	{
    	函数体;
        return val;
	}
}
2.访问限定符
  1. public:共有成员,可以在任何位置访问
  2. protracted:受保护成员,只能在本类中和子类中访问
  3. private:私有成员,只能在自己的成员函数访问
  • 在class中成员默认是private,在struct中成员默认是public
  • 一般把成员变量设置为private,把成员函数设置成public
3.对象的创建方法
  1. 在栈上创建 :
    • 类名 对象;
    • 类名 对象数组名[对象数量];
  2. 在堆上创建:
    • 类名* 对象指针 = new 类名;
    • 类名* 对象指针 = new 类名[对象数量];

十二 构造函数与初始化列表

1.什么是构造函数
  • 对象创建时自动调用函数叫构造函数,负责完成一些准备工作。
2.无参构造
  • 一个类中至少有一个构造函数,如果不显示实现,则编译器自动生成一个没有参数的构造函数也叫无参构造。
  • 如果显示实现了构造函数,则不生成无参构造,为了避免出错,最好再显示实现一个什么都不做的无参构造。
3.有参构造
  • 创建对象可以附带一些数据给构造用于初始化成员变量,这样就必须显示实现带参数的构造函数,构造函数的参数就是创建对象时传递的数据。
注意:
  • 可以给有参构造设置默认参数,这样可以达到无参构造的效果。
4.单参构造

如果构造函数只有一个,如此参数的类型与类类型之间有类型提升的特性。

​ 如果不想有类型提升的功能,可以在单参构造前加 explicit 关键,可以禁止单参构造的类型提升。

 class Test
        {
        public:
            Test(int n)
            {

            }
        };
Test t = 10; 自动调用单参构造而不会出错。
5. 初始化列表
  1. 格式
构造函数(参数):成员1(参数1),成员2(参数2//初始化列表
{

}

初始化列表就是一种给类的成员变量初始化的一种语法。

  1. 特点

    1. 可以区分同名的参数,和成员变量。

    2. 可以给类类型的成员变量的构造函数传递参数

    3. 可以给父类的构造函数传递参数

    4. 常属性的成员变量只能初始化列表中赋值

十三 对象的创建过程

  1. 分配类对象所需要的内存空间,无论栈或堆。

  2. 传递实参调用构造函数,完成如下任务:

    1. 执行初始化列表
    2. 根据继承表继承顺序调用父类的构造函数
    3. 根据成员变量的定义顺序调用成员变量的构造函数
    4. 执行构造构造函数中的代码

十四 类的声明与实现分开

1. 在头文件中声明类
class 类名:继承方式 父类
    {
        成员变量;
        ...
    public:
        类名(形参列表);
        返回值 成员函数(形参列表);
        ...
    };
2. 在源文件中实现类的成员函数
	#include "声明类的头文件"
    类名::类名(形参列表)
    {
        函数体;
    }
    返回值 类名::成员函数(形参列表)
    {
        函数体;
        return val;
    }
3. 注意:
  • 使用类中只需要包含类的头文件,然后使用者与类的源文件目标文件一起合并即可。

  • 如果类中的内容,可头文件全部实现。

类的成员变量初始化的一种语法。

  1. 特点

    1. 可以区分同名的参数,和成员变量。

    2. 可以给类类型的成员变量的构造函数传递参数

    3. 可以给父类的构造函数传递参数

    4. 常属性的成员变量只能初始化列表中赋值

十三 对象的创建过程

  1. 分配类对象所需要的内存空间,无论栈或堆。

  2. 传递实参调用构造函数,完成如下任务:

    1. 执行初始化列表
    2. 根据继承表继承顺序调用父类的构造函数
    3. 根据成员变量的定义顺序调用成员变量的构造函数
    4. 执行构造构造函数中的代码

十四 类的声明与实现分开

1. 在头文件中声明类
class 类名:继承方式 父类
    {
        成员变量;
        ...
    public:
        类名(形参列表);
        返回值 成员函数(形参列表);
        ...
    };
2. 在源文件中实现类的成员函数
	#include "声明类的头文件"
    类名::类名(形参列表)
    {
        函数体;
    }
    返回值 类名::成员函数(形参列表)
    {
        函数体;
        return val;
    }
3. 注意:
  • 使用类中只需要包含类的头文件,然后使用者与类的源文件目标文件一起合并即可。

  • 如果类中的内容,可头文件全部实现。

十五 析构函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值