1-初识C++

1、简介

  • 首先C++和C一样,都属于编译型的语言
  • C++和C一样,都属于强类型语言
  • C++对C完全兼容,对其做了优化并提供了更多的特性
    • 语言风格更加简洁
    • 类型检查更加严格
    • 支持面向对象编程
    • 支持操作符重载
    • 支持异常处理
    • 支持泛型编程

2、第一个C++程序

在学习C++之前,先看一下C的程序

#include <stdio.h>

int main(){
	printf("hello world");
	return 0;
}

在看一下C++的程序

#include <iostream>

int main(){
	std::cout<<"hello world"<<std::endl;
	return 0;
}

区别:

  • 编译器:
    • 编译C++使用的是g++,当然也可以用gcc,但要加上-lstdc++
  • 扩展名:
    • .cpp/.cc/.C/.cxx,甚至也可以可以用.c
  • 头文件:
    • #include<iostream>这个头文件在usr/include/c++目录中
  • 流操作:cout<< / cin >>
    • 也可以用printf/scanf
  • 所有标准库提供的类型、对象和函数都位于std名字空间中

2.1 名字空间(命名空间)

  • 概念:
    • 作用:划分更多的逻辑空间(逻辑单元、作用域),可以有效避免名字冲突
    • 定义:namespace 名字空间名{…}
namespace ICBC{
	int g_money = 0;
	void save(int money){ // 连声明带定义
		g_money = money;
	}
	void pay(int money);// 声明
}
void ICBC::pay(int money){ // 定义
	g_money -= money;
}
namespace CCB{
	int g_money = 0;
	void save(int money){
		g_money = money;
	}
}
namespace CCB{ // 编译器将合并为一个
	void pay(int money){
		g_money -= money;
	}
}
int main(){
	ICBC::save(1000);
	ICBC::pay(300);
	std::cout << "工行卡余额" << ICBC::g_money << std::endl;

	CCB::save(1000);
	CCB::pay(500);
	std::cout << "建行卡余额" << CCB::g_money << std::endl;
	return 0;
}
  • 使用
    • 使用作用域限定符 ::
    • 名字空间指令
    • 名字空间声明
// 名字空间指令
namespace ns{
	int g_value = 0;
}
int main(){
	using namespace ns; // 从这行代码开始,ns中的内容在当前作用域可见
	g_value = 100;
	std::cout << "ns::g_value=" << ns::g_value << std::endl;
	return 0;
}
// 名字空间声明
using namespace std;
int main(){
	using ns::g_value;// 从这行代码开始,ns中的g_value引入当前作用域(相当于定义) 
	g_value = 100;
	cout << "ns::g_value=" << ns::g_value << endl;
	return 0;
}
  • 名字空间指令和名字空间声明的差别
namespace ns1{
	int g_value = 0;
	int g_other = 0;
}
namespace ns2{
	int g_value = 0;
	int g_other = 0;
}
int main(){
	using namespace ns1; // 名字空间指令,ns1中所有内容都可见(出现在可见表中)
	using ns2::g_value; // 名字空间声明,只有ns2中的g_value相当于定义(出现在定义表中)
	g_value = 888; // 访问的是ns2的g_value
	cout << "ns1:g_value=" << ns1::g_value << ",ns2:g_value=" << ns2::g_value << endl;
	g_other = 666; // 访问的是ns1的g_other
	cout << "ns1:g_other=" << ns1::g_other << ",ns2:g_other=" << ns2::g_other << endl;
	return 0;
}

·名字空间嵌套
- 内层标识符与外层同名标识符为隐藏关系
- 嵌套的名字空间需要逐层分解

// 名字空间的嵌套
namespace na1{
	int g_value = 100;
	namespace na2{
		int g_value = 200;
		namespace na3{
			int g_value = 300;
			namespace na4{
				int g_value = 400;
			}
		}
	}
}
int main(){
	cout << na1::na2::na3::na4::g_value << endl;
	return 0;
}
  • 名字空间别名
    -可通过名字空间别名简化书写
int main(){
	namespace ns_for = na1::na2::na3::na4; // ns_for名字空间别名
	cout << ns_for::g_value << endl;
	return 0;
}

3、C++复合函数

  • C++的结构
    -定义结构型的变量时,可以省略struct关键字
    -可以定义成员函数,在结构体中的成员函数内部可以直接访问本结构体的成员,无需通过“.”或“->”
void TestStruct(){
	struct Student{
		int m_age; // 成员变量
		char m_name[256];
		void getinfo(){ // 成员函数  c语言中结构体中不能出现函数,c++中可以
			cout << "姓名:" << m_name << ",年龄:" << m_age << endl;
		}
	};
	Student s;// struct Student s 可以省略struct关键字
	s.m_age = 22;
	strcpy(s.m_name , "zs");
	s.getinfo();
}
  • C++的联合
    -定义联合型的变量时,可以省略union关键字
    -支持匿名联合
void TestUnion(){
	union { // 匿名联合体,主要体现各个成员内存的排布方式(共用一块内存空间)
		int i;
		char c[4];
	};
	i = 0x12345678; // 小端字节序:低数位占低地址
	cout << hex << (int)c[0] << " " << (int)c[1] << " " << (int)c[2] << " " << (int)c[3] << " " << i << endl;
}
  • C++的枚举
    -定义枚举型的变量时,可以省略enum关键字
    -独立的类型,和整型之间不能隐式转换
void TestEnum(){
	enum Color{ red, blue, green };
	Color c = red;
	// Color c = 0; //编译器报错
	cout << "c=" << c << endl;
}

4、布尔类型

布尔类型是C++中的基本类型

  • 表示布尔量的数据类型
    -bool
  • 布尔类型的字面值常量
    -true表示真
    -false表示假
  • 布尔类型的本质
    -单字节整数,用1和0表示真和假
  • 任何基本类型都可以被隐式转换为布尔类型
    -非0即真,0即假
void testBool(){
	bool a = true;
	bool b = false;
	bool c = 123;// 隐式转换
	bool d = "";// 空串并不是真的空,占一个字节的地址,这里真正给d的是此串的地址
	cout<<boolalpha << "a=" << a << " b=" << b << endl;
	cout << "c=" << c << " d=" << d << endl;
}

5、重载

5.1 重载关系

  • 同一作用域内,函数名相同,参数表不同
  • 根据实参类型和形参的类型进行匹配,调用最匹配的函数
  • 只有同一作用域内的同名函数才涉及重载的关系,不同作用域的同名函数涉及的是隐藏关系。
// 重载关系:1、同一个作用域内 2、函数名相同 3、形参表必须不同
// 形参表是否相同和形参名毫无关系和形参的个数以及每个对应形参的类型有关
// 形参本身常属性 不做区分
void foo(char *c, short s){
	cout << "1" << endl;
}
void foo(int i, double d){ cout << "2" << endl; }
void foo(const char *c, short s){ cout << "3" << endl; }// const char *c 这个const修饰的是指针的,而不是修饰c本身的 (指针常量和常量指针的区别)
void foo(double d, int i){ cout << "4" << endl; }
// void foo(char* const c, short s){}// 这个不构成重载

void foo(char a){}
//void foo(const char a){};// 不构成重载

void C_11(){
	char *c; short s = 0;
	foo(c, s);
	const char * b;
	foo(b, s);
}

5.2 重载解析

  • 完全匹配>常量转换>升级转换>标准转换>自定义转换>省略号匹配
// 完全匹配>常量转换>升级转换>标准转换>自定义转换>省略号匹配 在linux下测试
void bar(char *c, short s){ // 完全匹配
	cout << "1.bar" << endl;
}
void bar(const char * c, short s){ // 常量转换
	cout << "2.bar" << endl;
}
void bar(char * c, int s){// 升级转换
	cout << "3.bar" << endl;
}

void bar(char * c, char s){// 标准转换
	cout << "4.bar" << endl;
}
void bar(...){// 省略号匹配
	cout << "5.bar" << endl;
}

int main(){
	char * c = NULL; short s = 0;
	bar(c, s);// 普通方式调用,根据实际参数类型和形参类型进行匹配来确定到底调用哪个
	void(*pbar)(const char *, short) = bar;
	pbar(c, s); // 函数指针方式调用,根据函数指针本身的类型来确定到底调用的是哪个
	return 0;
}

5.3 重载的本质

  • 重载是通过C++换名机制来实现的
    在这里插入图片描述

通过extern “C”可以要求C++编译器按照C方式编译函数,即不做换名,当然也就无法重载。

extern "C" {
	int sum(int x,int y){
		return x+y;
	}
	int sub (int x,int y){
		return x-y;
	}
}

6、哑元函数

哑元函数:只指定形参类型而不指定形参名称的函数

  • 保证函数的向下兼容
  • 形成函数的重载版本
void fot(int){
	// 函数内部不能获取实参数据
	...
}

7、缺省(默认)参数

  • 可以为函数的形参指定缺省(默认)值,当调用该函数时若未指定实参,则使用形参的缺省(默认)值。
  • 如果函数的某一个形参具有缺省(默认)值,那么该形参后面的所有形参必须都具有缺省(默认)值
  • 尽量避免因为使用缺省参数而导致重载匹配歧义
  • 函数形参的缺省(默认)值只能在函数声明中指定
void foo(int a, double b, float c = 3.1, short d = 4, char e = 'A');// 声明
void foo(int a, double b, float c , short d, char e ){// 定义
	cout << e << endl;
}
int main(){
	foo(3, 3.14, 3.1, 2);
	foo(3, 3.14, 3.1, 2,'B');
	return 0;
}

8、内联函数

调用普通函数的问题:
每个普通函数调用语句都需要发生跳转操作,这种跳转操作会带来时间开销。
内联就是用函数已被编译好的二进制代码,替换对该函数的调用指令,内联在保证函数特性的同时,避免了函数调用的时间开销

void f1(int a){ // 普通函数
	cout << "f1(int a)" <<a<< endl;
}
inline void f2(int a){// 内联函数
	cout << "f2(int a)"<<a << endl;
}
int main(){
	f1(1); // 需要生成跳转指令
	f2(2); // 将此处替换为f2函数编译后产生的二进制指令集,此处是在正式编译阶段
	return 0;
}

注意:

  • 内联会使可执行文件的体积和进程代码的内存变大,因此只有频繁调用的简单函数才适合内联
  • inline关键字仅表示期望该函数被优化为内联,但是否适合内联则完全由编译器决定
  • 稀少被调用的复杂函数和递归函数都不适合内联

9、动态内存分配

  • 可以继续使用标准C库函数malloc/free
  • 更建议使用new/delete操作符在堆中分配/释放内存
  • 在分配内存的同时初始化
  • 以数组方式new的也要以数组方式delete
  • 通过new操作符分配N维数组,返回N-1维数组指针
  • 不能通过delete操作符释放已释放过的内存
  • delete野指针后果未定义,delete空指针安全
  • new操作符申请内存失败,将抛出异常
int main(){
	int *pm = (int *)malloc(4);
	cout << *pm << endl;
	free(pm);

	int *pn = new int(100); // 分配内存的同时可以初始化
	cout << *pn << endl;
	delete pn;// 执行结束后pn指向的堆内存被释放了,进而pn变为悬空(野)指针

	int * parr = new int[4]{3, 4, 6};// 以数组方式new的,永远返回第一个(首)元素的地址
	for (int i = 0; i < 4; i++){
		cout << parr[i] << " ";
	}
	delete[] parr;// 数组方式new的也要用数组的方式删除

	int(*p)[4] = new int[3][4];// 返回一个一维数组指针
	delete[]p;
	//	delete pn;//再次删除pn时,此时pn为野指针,释放野指针后果未定义,delete操作符释放已释放过的内存
	pn = NULL; // 建议每次释放完内存后,指针置空
	delete pn; // 释放空指针是安全的
	cout << endl;
	try{
		new int[0x7ffffff];// new操作符申请内存失败,将抛出异常
	}
	catch (...){// 捕获异常
	}
	return 0;
}

10、左值和右值

左值:能够取地址
右值:不能够取地址

int main(){
	// 左值:具名内存,能够取址  生命周期是当前的作用域 
	// 非常左值:无const修饰
	// 常左值:有const修饰
	int a = 10;
	&a;
	const int b = 10;
	&b;
	// 右值:不能取地址,是个匿名内存  生命周期是语句级的生命周期
	// C++98/03标准给出结论是:更改右值毫无意义
	// C++11标准不这样认为的 见C++11
	10;
	// &10;// err
	return 0;
}

11、引用

11.1 引用

  • 引用即内存的别名
int a=10;
int &b =a;// 引用
  • 引用本身不占内存,并非实体, 对引用的所有操作都是在对目标内存进行操作
  • 引用必须初始化,且不能更换目标
  • 不存在引用的引用
  • 引用的常属性须和目标的常属性保持“一致”
  • 可以限定更加严格
  • 引用可以延长右值的生命周期
  • 常引用 即 万能引用
  • 引用的生命周期不能长于目标
int main(){
	int a = 10;
	int &b = a;//不要理解为a给b赋值,而应该理解为b是目标内存(a)的别名
	b = 20;// 对引用b赋值,其实是在对引用b的目标内存(a)赋值
	cout << "a=" << a << ",b=" << b << endl;// 读取的为引用b的值,其实读取的是为引用b的目标内存(a)的值
	cout << "&a=" << &a << ",&b=" << &b << endl; // 取的是引用b的地址,其实取的是引用b的目标内存(a)的地址
	//int &c;引用必须初始化
	int d = 20;
	b = d;// 仅仅是在对引用的目标内存进行赋值,而不是将引用b的目标内存的更改为d
	cout << "a=" << a << endl;
	cout << "&a=" << &a << ",&b=" << &b << ",&d=" << &d << endl;
	int &e = b;// 这里不要理解为e是b的别名,而应该是b和e都是目标内存(a)的别名
	cout << "&a=" << &a << ",&b=" << &b << ",&e=" << &e << endl;
	const int f = 10;
	// int& g = f;//ERROR 引用的常属性须和目标的常属性保持“一致”
	const int& h = f;//OK
	const int & cra = a;// 限制得更加严格,别名可以比真名限制得更加严格
	const int & ri =10;//这个可以,引用可以延长右值的生命期
	// int &r =10;// 不可以
}

11.2 引用的应用

  • 引用型参数,函数的形参是实参的别名,避免对象复制的开销
    • 非常引用型参数:可以在函数中修改值
    • 常引用型参数:不可以在函数中修改值
void swap(int &a, int &b){// 非常引用型参数:可以在函数中修改值
	int z = a;
	a = b;
	b = z;
}
void Print(const int& x, const int& y){ // 常引用型参数:不可以在函数中修改值
	cout << x << y << endl;
}

void swap(int *a, int *b){
	int z = *a;
	*a = *b;
	*b = z;
}
int main(){
	int a = 3, b = 4;
	swap(&a, &b);// 指针(地址值的传递)
	cout << "a:" << a << " b:" << b << endl;
	swap(a, b); // 引用
	cout << "a:" << a << " b:" << b << endl;
	Print(a, b);
	Print(666, 888);
	return 0;
}
  • 引用型的返回值,从函数中返回引用,一定要保证在函数返回以后,该引用的目标依然有效
    • 可以返回全局、静态变量的引用
    • 可以返回成员变量的引用
    • 可以返回在中动态创建的对象的引用
    • 可以返回调用对象自身的引用
    • 可以返回引用型参数本身
    • 不能返回局部变量的引用
      -非常引用型返回值->通过引用可以修改目标
      -常引用型返回值->通过引用不可以修改目标
int g_value = 0;
// 可以返回全局、静态变量的引用
int& foo(){ // 非常引用型返回值->通过引用可以修改目标
	return g_value;
}
const int & fooo(){ //常引用型返回值->通过引用不可以修改目标
	return g_value;
}
int& bar(){
	static int s_static = 100; // 生命期是进程的生命周期,这一行是程序启动就执行而且只执行一次,并不是每次调用bar函数才执行
	cout << "s_static:" << s_static << endl;
	return s_static;
}
// 可以返回在堆中动态创建的对象的引用
int& hum(){
	int *p = new int(10);
	return *p;
}
//可以返回引用型参数本身
int& fun(int& x){
	return x;
}
//不能返回局部变量的引用
int& boo(){
	int a = 10;
	return a;// 这种是不可以的 保证不了在函数返回以后,该引用的目标依然有效
}
int main(){
	foo()=100;
	cout << "g_value=" << g_value << endl;
	bar()=200;
	bar();
	hum() = 300;
	int a_value = 0;
	fun(a_value)=200;
	cout <<a_value << endl;
	boo();
//	fooo() = 1000; // 报错,这里不可以修改值
	return 0;
}

11.3 引用与指针

实现层面,引用就是指针,但在C++语言层面,引用不是实体类型,因此C++语言层面引用与指针存在明显的差别

  • 指针可以不初始化,而引用必须初始化。
  • 指针的目标可在初始化后随意变更(除非是指针常量),而引用一旦初始化就无法变更其目标
  • 存在空指针,不存在空引用
  • 存在指向指针的指针,不存在引用的引用
  • 存在指针的引用,不存在引用的指针
  • 存在指针数组,不存在引用数组,但存在数组引用
int main(){
	int a = 10, b = 20;
	int *pa;// 指针可以不做初始化,也可以初始化 int *pa=&a;
	int &ra = a;// int &ra;// 错误 引用必须初始化

	pa = &b; // 指针的目标内存可以随意变更
	ra = b; // 引用的目标内存不会变更,这里仅仅是利用b中的数据给ra的目标内存(a)赋值

	pa = NULL;// 存在空指针
	// ra = NULL; // 不存在空引用

	int **ppa = &pa;// 存在二级指针
	// int && rra = ra;// 直接报错 不存在二级引用

	int* &rpa = pa;// 存在指针的引用
	// int& *rpb; // 报错 不存在引用的指针

	int *arr[4] = { &a, &b, &a };// 存在指针的数组
	//int &raar[3] = { a, b, a };// 报错,不存在引用的数组
	int c[3];
	int(&raar)[3] = c;// 存在数组的引用
	return 0;
}

12、类型强转

  • C风格的显式类型转换(不建议)
    -(目标类型)源类型变量
  • C++风格的显式类型转换(不建议)
    -目标类型(源类型变量)
int main(){
	int a; double b; float c; short d; char e;
	// 任何基本类型的变量之间都可以 隐式转换
	a = b = c = d = e;

	// 任何其他类型的指针 到 void * 都可以隐式转换
	void *pv = &a;

	// void * 到 任何其他类型的指针 都必须强制转换
	int *pa = (int *)pv;
	double *pb = (double*)(pv);

	// 任何类型的非常指针 到 同类型的常指针 都可以隐式转换
	const int * cpa = pa;

	// 任何类型的常指针 到 同类型的非常指针都必须 强转
	int *pca = (int *)cpa;
	
	// 除了void* 外 任何其他类型的指针之间 都必须强转
	pb = (double *)pa;
	return 0;
}
  • 静态类型转换
    -static_cast<目标类型> (源类型变量)
    -隐式类型转换的逆转换
    -自定义类型转换
  • 动态类型转换
    -dynamic_cast<目标类型> (源类型变量)
    -多态父子类指针或引用之间的转换
  • 常类型转换
    -const_cast<目标类型> (源类型变量)
    -去除指针或引用上的const属性
  • 重解释类型转换
    -reinterpret_cast<目标类型> (源类型变量)
    -任意类型的指针之间的转换或引用之间的转换
    -任意类型的指针和整型之间的转换
int main(){
	int a=0; double b=0; float c=0; short d=0; char e=0;
	// 任何基本类型的变量之间都可以 隐式转换
	a = b = c = d = e;

	// 任何其他类型的指针 到 void * 都可以隐式转换
	void *pv = &a;

	// void * 到 任何其他类型的指针 都必须强制转换
	int *pa = (int *)pv; 
	int * pc = static_cast<int *>(pv);// void* --> int*的反向 int* -->void*可以隐式
	double *pb = (double*)(pv);

	// 任何类型的非常指针 到 同类型的常指针 都可以隐式转换
	const int * cpa = pa;

	// 任何类型的常指针 到 同类型的非常指针都必须 强转
	int *pca = (int *)cpa;
	int *pcb = const_cast < int *>(cpa); // const int *-->int * 的反向int * -->const int *可以隐式 
	
	// 除了void* 外 任何其他类型的指针之间 都必须强转
	pb = (double *)pa;
	pb = reinterpret_cast<double*>(pa);
	return 0;
}
  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

启航zpyl

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

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

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

打赏作者

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

抵扣说明:

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

余额充值