C++入门基础

目录

C++入门基础

 一、第一个C++程序

二、命名空间

2.1.C语言命名冲突问题

2.2.namespace的定义

2.3.namespace的本质

2.4.C++中的作用域

2.5.namespace的嵌套定义

2.6.namespace的跨文件扩展

2.7.std标准命名空间

2.8.namespace的使用

三、C++的输入输出

3.1.C语言输入输出局限

3.2.输入输出流

3.3.流插入(cout)

3.4.流提取(cin)

3.5.IO效率提高

 四、缺省参数(默认参数)

4.1.全缺省

4.2.半缺省

4.3.缺省参数的作用 

五、函数重载

5.1.参数的类型不同

5.2.参数的个数不同

5.3.参数类型顺序不同

5.4.特殊情况

六、引用

6.1.引用的概念

6.2.引用的特性

6.3.指针 VS 引用

6.4.引用的使用

七、const引用

7.1.权限问题

7.2.常量取别名

7.3.临时对象

7.4.引用传参规则

八、inline内联

8.1.C语言宏函数局限

8.2.内联函数

九、nullptr关键字


 一、第一个C++程序

#include <iostream>
using namespace std;

int main()
{
	cout << "hello world\n" << endl;
	return 0;
}

二、命名空间

2.1.C语言命名冲突问题

C语言的变量、函数的名称都存在于全局作用域中,可能会导致冲突

C++引入namespace关键字,对标识符的名称进行本地化,避免问题

2.2.namespace的定义

namespace + 命名空间的名字,{}内为命名空间的成员

命名空间中可以定义变量、函数、类型等

printf函数访问的是全局域中rand函数,而非命名空间域中的rand变量

2.3.namespace的本质

定义出一个域,这个域全局域各自独立,不同的域可以定义同名变量

域作用限定符::: 

在全局域中创建一个变量a,在main函数中也创建一个变量a

如果在main函数中打印,则会优先打印main函数中的变量a

如果想打印全局域中的变量a,就需要在a前加域作用限定符

要使用域中的变量,需要在变量前加域名称域作用限定符

示例:

2.4.C++中的作用域

函数局部域 全局域

命名空间域 类域

域隔离解决了名字冲突的问题

局部域和全局域不仅会影响编译查找逻辑,还会影响变量生命周期

域影响的是编译时语法查找一个变量、函数、类型声明或定义的逻辑

2.5.namespace的嵌套定义

#include <iostream>​​​​​​

namespace bit
{
	// 鹏哥
	namespace pg
	{
		int rand = 1;

		int Add(int left, int right)
		{
			return left + right;
		}
	}

	// 杭哥
	namespace hg
	{
		int rand = 2;

		int Add(int left, int right)
		{
			return (left + right) * 10;
		}
	}
}

int main()
{
	printf("%d\n", bit::pg::rand);
	printf("%d\n", bit::hg::rand);

    printf("%d\n",bit::pg::Add(1,2));
    printf("%d\n",bit::hg::Add(1,2));

	return 0;
}

2.6.namespace的跨文件扩展

namespace可以在多个文件中重复定义同一命名空间域

示例:namespace定义栈的头文件与源文件

Stack.h:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

namespace bit
{
	typedef int STDataType;
	typedef struct Stack
	{
		STDataType* a;
		int top;
		int capacity;
	}ST;

	void STInit(ST* ps, int n);
	void STDestroy(ST* ps);
	void STPush(ST* ps, STDataType x);
	void STPop(ST* ps);
	STDataType STTop(ST* ps);
	int STSize(ST* ps);
	bool STEmpty(ST* ps);
}

Stack.cpp:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

namespace bit
{
	 void STInit(ST * ps, int n)
	 {
		assert(ps);
		ps->a = (STDataType*)malloc(n * sizeof(STDataType));
		ps->top = 0;
		ps->capacity = n;
	 }
	
	// 栈顶
	void STPush(ST * ps, STDataType x)
	{
		assert(ps);
		
		// 满了, 扩容
		if (ps->top == ps->capacity)
		{
			printf("扩容\n");
			int newcapacity = ps->capacity == 0 ? 4 : ps->capacity* 2;
			STDataType * tmp = (STDataType*)realloc(ps->a,
			newcapacity * sizeof(STDataType));
			if (tmp == NULL)
			{
				 perror("realloc fail");
				 return;
			}
			ps->a = tmp;
			ps->capacity = newcapacity;
		 }
		ps->a[ps->top] = x;
		ps->top++;
	}
	//...
}

test.cpp:

#include"Stack.h"

// 全局定义了⼀份单独的Stack
typedef struct Stack
{
	int a[10];
	int top;
}ST;
void STInit(ST* ps) {}
void STPush(ST* ps, int x) {}


int main()
{
	// 调⽤全局的
	ST st1;
	STInit(&st1);
	STPush(&st1, 1);
	STPush(&st1, 2);
	printf("%zd\n", sizeof(st1));
	// 调⽤bit namespace的
	bit::ST st2;
	printf("%zd\n", sizeof(st2));
	bit::STInit(&st2, 4);
	bit::STPush(&st2, 1);
	bit::STPush(&st2, 2);

	return 0;
}

2.7.std标准命名空间

C++标准库都放在一个叫std(standard)的命名空间中

2.8.namespace的使用

指定命名空间访问

#include <stdio.h>
int a = 0;
int main()
{
	int a = 1;
	printf("%d\n", a);
	printf("%d\n", ::a);
	return 0;
}

using将命名空间中所有成员展开

#include <stdio.h>
namespace bit
{
	int a = 0;
	int b = 1;
}

using namespace bit;

int main()
{
	printf("%d\n", a);
	printf("%d\n", b);
	return 0;
}

using将命名空间中某个成员展开

#include <stdio.h>
namespace bit
{
	int a = 0;
	int b = 1;
}

using bit::a;

int main()
{
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", bit::b);
	return 0;
}

三、C++的输入输出

3.1.C语言输入输出局限

C语言的输入输出需要手动指定变量类型

C++的输入输出可以自动识别变量类型(函数重载)

更好的支持自定义类型对象的输入输出

3.2.输入输出流

<iostream>(Input Output Stream):标准输入输出流库,定义了标准的输入、输出对象

std::cin:istream类的对象,主要面向窄字符的标准输入流

std::cout:ostream类的对象,主要面向窄字符的标准输出流

std::endl:函数、流插入输出时,相当于插入

<<:流插入运算符

>>:流提取运算符

注:窄字符(narrow characters (of type char))

内存中有整型、浮点型的存储形式

但转入其他文件后就只有字符形式

示例:

#include <iostream>

int main()
{
	int i = 1234;
	std::cout << i << std::endl;
	return 0;
}
#include <iostream>
using namespace std;

int main()
{
	int i = 1234;
	cout << i << endl;
	return 0;
}

将内存中变量i的二进制数据以字符形式插入到标准输出流(控制台)中

3.3.流插入(cout)

#include <iostream>
using namespace std;

int main()
{
	int a = 0;
	double b = 0.1;
	char c = 'x';

	cout << a << " " << b << " " << c << " ";
	cout << a << " " << b << " " << c << endl;
	cout << a << " " << b << " " << c << '\n';
	cout << a << " " << b << " " << c << " ";
	return 0;
}

注:

流插入运算符<<是一个二元操作符,只能有两个操作数

endl函数具有换行的作用

3.4.流提取(cin)

#include <iostream>
using namespace std;

int main()
{
	int a;
	double b;
	char c;

	cin >> a;
	cin >> b >> c;

	cout << a << endl;
	cout << b << " " << c << endl;

	return 0;
}

注:

流提取运算符>>不需要输入变量的地址

3.5.IO效率提高

在io需求比较高的地方,比如部分大量输入的竞赛题中

加上以下3行代码,可以提高C++的IO效率

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	return 0;
}

 四、缺省参数(默认参数)

缺省参数:声明或定义函数时为函数参数指定一个缺省值

没有传参时:使用参数的默认值(缺省值)

有参数传送:使用指定的实参

#include <iostream>
using namespace std;

void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func();
	Func(10);

	return 0;
}

4.1.全缺省

全部形参给缺省值

#include <iostream>
using namespace std;

// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

int main()
{
	Func1();
	Func1(1);
	Func1(1, 2);
	Func1(1, 2, 3);
	return 0;
}

注:错误传法

Func1( ,2, );

4.2.半缺省

部分形参给缺省值

必须从右往左连续缺省,不能间隔、跳跃

#include <iostream>
using namespace std;

//半缺省
void Func2(int a, int b = 10, int c = 20)
{
	 cout << "a = " << a << endl;
	 cout << "b = " << b << endl;
	 cout << "c = " << c << endl << endl;
}

int main()
{
    Func2(100);
	Func2(100, 200);
	Func2(100, 200, 300);
	return 0;
}

4.3.缺省参数的作用 

// Stack.h

#include <iostream>
#include <assert.h>
using namespace std;

typedef int STDataType;
typedef struct Stack
{
	STDataType * a;
	int top;
	int capacity;
}ST;
void STInit(ST * ps, int n = 4);

// Stack.cpp

#include"Stack.h"
// 缺省参数不能声明和定义同时给
void STInit(ST * ps, int n)
{
	assert(ps && n > 0);
	ps->a = (STDataType*)malloc(n * sizeof(STDataType));
	ps->top = 0;
	ps->capacity = n;
}

// test.cpp

#include"Stack.h"
int main()
{
	ST s1;
	STInit(&s1);
	
	// 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容
	ST s2;
	STInit(&s2, 1000);
	
	return 0;
}

注:

函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现

规定必须在函数声明时给缺省值

五、函数重载

C++支持在同一作用域中出现同名函数

但是要求同名函数的形参不同

5.1.参数的类型不同

#include <iostream>
using namespace std;

// 1、参数类型不同
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	
	return left + right;
}

double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

int main()
{
	Add(1, 2);
	Add(1.1, 1.1);
	return 0;
}

5.2.参数的个数不同

#include <iostream>
using namespace std;

// 2、参数个数不同
void f()
{
	cout << "f()" << endl;
}

void f(int a)
{
	cout << "f(int a)" << endl;
}

int main()
{
	f();
	f(10);
	return 0;
}

5.3.参数类型顺序不同

#include <iostream>
using namespace std;

// 3、参数类型顺序不同
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}

void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}

int main()
{
	f(10, 'a');
	f('a', 10);
	return 0;
}

5.4.特殊情况

返回值无法作为函数重载条件,因为调用时也无法区分

//返回值不同不能作为重载条件,因为调⽤时也⽆法区分
void fxx()
{
}

int fxx()
{
    return 0;
}

下面两个函数构成重载

f()中无实参时,调用时会报错,存在歧义,编译器不知道调用谁

f()中有实参时,就明确要调用第二个f1函数

void f1()
{
    cout << "f()" << endl;
}

void f1(int a = 10)
{
    cout << "f(int a)" << endl;
}

解决方案:将两个函数放在不同的域中(此时不为函数重载

#include <iostream>
using namespace std;

namespace bit1
{
	void f1()
	{
		cout << "f()" << endl;
	}
}

void f1(int a = 10)
{
	cout << "f(int a)" << endl;
}
int main()
{
	bit1::f1();
	f1();
	return 0;
}

六、引用

6.1.引用的概念

引用:给已存在变量取一个别名

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	
	// 引⽤:b和c是a的别名
	int& b = a;
	int& c = a;

	// 也可以给别名b取别名,d相当于还是a的别名
	int& d = b;
	++d;

	// 这⾥取地址我们看到是⼀样的
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &d << endl;

	return 0;
}

6.2.引用的特性

引用在定义时必须初始化

一个变量可以有多个引用

引用一旦引用一个实体,再不能引用其他实体

6.3.指针 VS 引用

引用:

变量的别名,本身无独立地址,无法被重新赋值,对引用修改就是对原变量的修改

指针变量:

独立的变量,本身有独立地址,存放原变量的地址,可以被重新赋值,指针变量要修改它指向的变

量就需要进行解引用

引用 指针变量
语法概念 一个变量取的别名(不开空间) 存储一个变量的地址(要开空间)
初始化 定义时必须初始化 定义是最好初始化
对象存储 不可以再引用其他对象 可以再不断改变指向对象
访问对象 直接访问 解引用访问
类型大小 引用类型的大小 地址空间所占字节个数
安全性 问题较少,相对安全 容易出现空指针与野指针问题

6.4.引用的使用

改变引用对象时同时改变被引用对象

避免拷贝,提高效率,语法更加简洁

引用传参

C语言中的传参只有两种:传值调用,传址调用

规则简单,无论是常数还是变量,想传就传

不需要区分左值、右值、常量、临时对象

C++中因为有引用的概念,能够给变量起别名,所以可以引用传参

这种传参方式不需要临时拷贝,提高了效率,但传参规则更加严格

#include<iostream>
using namespace std;

void Swap(int& rx, int& ry)
{
	int tmp = rx;
	rx = ry;
	ry = tmp;
}

int main()
{
	int x = 0, y = 1;
	cout << x << " " << y << endl;

	Swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

引用做返回值

正常返回时会生成临时对象,将其作为返回值

这种对象具有常性,类似const修饰,无法修改

#include<iostream>
#include <assert.h>

using namespace std;

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST& rs, int n = 4)
{
	rs.a = (STDataType*)malloc(n * sizeof(STDataType));
	rs.top = 0;
	rs.capacity = n;
}

void STPush(ST & rs, STDataType x)
{
	if(rs.top == rs.capacity)
	{
		printf("扩容\n");
		int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
		STDataType * tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		rs.a = tmp;
		rs.capacity = newcapacity;
	}
	rs.a[rs.top] = x;
	rs.top++;
}

void STModifyTop(ST& rs, int x)
{
	rs.a[rs.top - 1] = x;
}

int STTop(ST & rs)
{
	assert(rs.top > 0);
	
	return rs.a[rs.top];
}

int main()
{
	ST st1;
	STInit(st1);
	STPush(st1, 1);
	STPush(st1, 2);

	STTop(st1) = 3;
	
	return 0;
}

引用做返回值时,返回的是返回值的别名

中间没有产生临时对象,可以进行修改

示例:此时STModifyTop与STTop的效果一样

#include<iostream>
#include <assert.h>

using namespace std;

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST& rs, int n = 4)
{
	rs.a = (STDataType*)malloc(n * sizeof(STDataType));
	rs.top = 0;
	rs.capacity = n;
}

void STPush(ST & rs, STDataType x)
{
	if(rs.top == rs.capacity)
	{
		printf("扩容\n");
		int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
		STDataType * tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		rs.a = tmp;
		rs.capacity = newcapacity;
	}
	rs.a[rs.top] = x;
	rs.top++;
}

void STModifyTop(ST& rs, int x)
{
	rs.a[rs.top - 1] = x;
}

int& STTop(ST & rs)
{
	assert(rs.top > 0);
	
	return rs.a[rs.top];
}

int main()
{
	ST st1;
	STInit(st1);
	STPush(st1, 1);
	STPush(st1, 2);

	STModifyTop(st1, 4);
	STTop(st1) = 4;
	
	return 0;
}

注:

引用做返回值时,如果返回值对象为栈区的局部变量,出了作用域,函数栈帧就会被销毁,虽然返

回引用,但返回值已经销毁,此时引用就会成类似野指针的情况

示例:

变量a为栈区局部变量,func函数执行完毕后,函数栈帧销毁,变量a的内存释放

此时用悬垂引用赋值1,修改的是一块已经被销毁的空间,所以依旧打印1000

而如果返回值的对象为堆区或者全局的变量时,出了函数依旧能够找到原来对象的空间

七、const引用

7.1.权限问题

如果一个变量被const修饰,那么就必须要在它的引用前加const

对象的访问权限在引用过程中可以缩小,但是不能放大

注:b变量可读可写的,经过权限缩小后的rb只能读取

区分权限放大与赋值拷贝:

7.2.常量取别名

可以在引用前加const给常量取别名

7.3.临时对象

编译器需要一个空间暂存表达式的求值结果时,临时创建的一个未命名的对象,称为临时对象

临时对象一般指表达式右值非引用类型的函数返回值,发生类型转换的变量

临时对象引用需要const修饰,不能进行修改

发送类型转换的变量也会产生临时对象

7.4.引用传参规则

普通引用:T&:表达式左值(有名字的变量)

常量引用:const T&:表达式左值,以及临时对象(表达式右值,常量、发生类型转换后的值)

注:常量引用的表达式左值在函数体内无法进行修改

八、inline内联

8.1.C语言宏函数局限

C语言实现宏函数会在预处理时替换展开

但宏函数实现很复杂,并且很容易出错,不方便调试

C++设计了inline代替C语言的宏函数

不能加分号

要加外括号

要加内括号

8.2.内联函数

内联函数:用inline修饰的函数

核心优势:

速度快:

省去了普通函数的所有开销(开辟栈帧、拷贝参数、跳转执行、销毁栈帧、返回数据)

直接在调用处展开内联函数并且执行函数体,效率更接近宏函数(汇编中无call指令)

更安全:

不像宏函数是纯文本替换,内联函数有严格的类型检测和作用域

避免了宏函数中运算优先级问题

使用场景:

inline适用于频繁调用的短小函数

对于递归函数,代码量大的一些函数,加上inline也会被编译器忽略

因为inline对于编译器而言只是一个建议

注:

inline不建议将声明和定义放在不同文件,因为分离会导致链接错误

因为inline被展开就没有函数地址,链接时会出现报错

一般内联函数直接放在头文件中,便于展开

vs编译器debug版本下默认不展开inline,便于调试,想展开需要手动设置

九、nullptr关键字

NULL实际是一个宏,在传统的C头文件(stddef.h)中,代码如下

#ifndef NULL
	#ifdef __cplusplus
		#define NULL 0
	#else
		#define NULL ((void*)0)
	#endif
#endif

在C语言中,NULL被定义成无类型指针((void*)0)的常量

在C++中,NULL可能被定义为字面常量0

如果想通过f(NULL)调用指针版本的f(int* ptr)函数

但由于NULL被定义成了0,调用了f(int x)函数

而如果将实参改为(void*)NULL调用则会报错

只能将实参改为(int*)NULL才能够正常执行

C++11中引入了nullptr关键字

是一种特殊类型的字面量,可以转换成任意其他类型的指针类型

使用nullptr定义空指针可以避免类型转换的问题

因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐