【C++】C++语言初步

基本信息

书籍:C++面向对象程序设计——清华出版社,李晋江 刘培强
书中代码环境:Visual C++2008 Windows XP
本人代码运行环境:Vs2019 Windows10
主要是复习C++,适合有基础的人看。

1 C++语言初步

书本第三章

1.1 Hello,C++

main.cpp

#include <iostream>
using namespace std;

int main() {
	std::cout << "Hello C++" << std::endl;
	int i;
	std::cin >> i;
	std::cout << "i=" << i << std::endl;
	return 0;
}

1.2 名字空间

iostream输入输出流

命名空间namespace:为解决C++中名字冲突而引入的。

#include <iostream> 
int main_helloC();
using namespace std;

int main_helloC() {

	std::cout << "Hello C++" << std::endl;
	int i;
	std::cin >> i;
	std::cout << "i=" << i << std::endl; 
	return 0;
}

对不同文件中的同名变量,可使用命名空间的方式进行区分

study3_1a.h

#include <iostream> 
 
namespace MyNameSpace1 {
	class CHello {
	public:
		void print();
	};
}

study3_1a.c

#include "study3_1a.h"  

void MyNameSpace1::CHello::print() {
	std::cout << "1" << std::endl;

}

study3_1b.h

#include <iostream> 
 
namespace MyNameSpace2 {
	class CHello {
	public:
		int i;
		void print();
	};
    /// 命名空间可嵌套
    namespace Inner {
        struct point{
            int x;
            int y;
        };
        void set(point &p, int i, intj){
            p.x = i;
            p.y = j;
        }
		int index;
	} 
}

study3_1b.cpp

#include "study3_1b.h" 
 
void MyNameSpace2::CHello::print() {
	this->i = 10;
	std::cout << this->i << std::endl;
	MyNameSpace2::Inner::index = 0;

	std::cout << MyNameSpace2::Inner::index << std::endl;
}

main.cpp

#include <stdio.h>
#include "study3_1a.h"
#include "study3_1b.h"

int main() { 
	MyNameSpace1::CHello x;
	x.print();
	MyNameSpace2::CHello x2;
	x2.print();
	
	return 0;
}

域操作符 ::

C++中可使用未命名的名字空间(unnamed namespace)声明一个局部于某一文件的实体。该名字空间空声明的名称作用域为:从声明到该声明区域末尾。下面的的show()函数相当于A.cpp的全局函数,若还有其他同名show()函数会出错。

A.cpp

namespace{
	void show(){std::cout<<"hello, unnamed namespace"<<endl;}
}
void show(); // error

同一个文件中的多个未命名的名字空间,若他们位于不同的作用域,在它们名字空间中可以有相同的成员。

B.cpp

#include <iostream>
using namespace std;

namespace{
	void show(){std::cout<<"hello, unnamed namespace"<<endl;}
}

namespace aaa{
	namespace{
		void show(){std::cout<<"hello, unnamed namespace"<<endl;
	}

}

int main()
{
    show();
    aaa::show();
    return 0;
}

通过命名空间实现函数重载。

#include <iostream> 

namespace A{
	void f(char c){std::cout<<"char is"<<c<<std::endl;}
}

namespace B{
	void f(int i){std::cout<<"int is"<<i<<std::endl;}
}

int main(){
    using A::f;
    using B::f;
    f('a');
    f(1);
    return 0;
}

命名空间是开放的,可通过后续代码加入新的成员。

namespace A{
	int i;
} // A有成员 i

namespace A{
	int j;
	void func();
} // A有成员 i,j,func()

1.3 输入输出

输入输出是一种数据传送操作,可以视为字符序列在主机和外设之间的流动。iostream为内置类型对象提供了输入输出支持,同时也支持文件的输入输出,类的设计者可以通过对iostream库的扩展,即重载>><<来支持自定义类型的输入输出操作。

1.3.1 cout输出

标 志作 用
ios::boolapha把 true 和 false 输出为字符串,而不是0或1
ios::left输出数据在本域宽范围内向左对齐
ios::right输出数据在本域宽范围内向右对齐
ios::internal数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充
ios::dec设置整数的进制为 10
ios::oct设置整数的进制为 8
ios::hex设置整数的进制为 16
ios::showbase强制输出整数的基数(八进制数以 0 开头,十六进制数以 0x 打头)
ios::showpoint强制输出浮点数的小点和尾数 0
ios::uppercase在以科学记数法格式 E 和以十六进制输出字母时以大写表示
ios::showpos对正数显示“+”号
ios::scientific浮点数以科学记数法格式输出
ios::fixed浮点数以定点格式(小数形式)输出
ios::unitbuf每次输出之后刷新所有的流
ios_base::floatfield设置输出时按浮点格式,小数后有6位数字
ios::skipws忽略前导的空格(主要用于输入流,如cin)

cout.cpp

#include <iostream>
#include <iomanip>
#include <windows.h>
#include <cstdio>
using namespace std;
void cout_format(); 

void cout_format(){
	float pi = 3.14159f;
	std::cout << pi << std::endl;		// 3.14159
	std::cout.precision(2); 
	std::cout << pi << std::endl;		// 3.1
	std::cout << std::setprecision(2);
	std::cout << pi << std::endl;		// 3.1 

	std::cout.flags(std::ios::left);
	std::cout << std::setw(10) << -123.45 << "end" << std::endl; 
	std::cout.flags(std::ios::internal);
	std::cout << std::setw(10) << -123.45 << "end" << std::endl;
	std::cout.flags(std::ios::right);
	std::cout << std::setw(10) << -123.45 << "end" << std::endl;
	std::cout << std::setfill('*') << std::setw(10) << -123.45 << "end" << std::endl;
	std::cout << std::showbase << std::hex<< 12 << std::setw(4) << std::dec<<12<< std::setw(4) << std::oct << 12 << std::endl;
	std::cout.setf(std::ios::adjustfield);
	std::cout << pi << std::endl;		 

	char* pbuffer = new char[1024];
	// setbuf(stdout, pbuffer); // C4996 'setbuf': This function or variable may be unsafe.Consider using setvbuf instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS.See online help for details.StudyCpp	D : \CodeForC\StudyCpp\study3_2.cpp	25

	setvbuf(stdout, pbuffer, _IOFBF, 1024);
	/*
	_IOFBF	全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。
	_IOLBF	行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
	_IONBF	无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。
	*/
    // 在执行输出时,数据会先进入一个缓冲区,当接收设备空闲时,再由缓冲区写入,可通过操纵符flush进行强制刷新。也可通过`setvbuf`中_IONBF达到同样的效果。
	std::cout << "hello, 1\n" << std::flush; 
	Sleep(1000);
	std::cout << "hello, 2\n" << std::flush;
	Sleep(1000);
	std::cout << "hello, 3" << std::flush;
	delete pbuffer;
}

cin.cpp

void cin_format(){

	char c;
	char cz[20];
	c = cin.get();				// 读取单个字符
	cout << c << endl;

	cin.getline(cz, 20);		// 获得字符串,最大长度为20,默认回车结束
	cout << cz << endl;
	cin.getline(cz, 20, 'h');	// 获得字符串,最大长度为20,检测到'h'作为输入结束
	cout << cz << endl;

	cout << "*********************" << endl;
	int a;
	cin >> a;
	cout << cin.rdstate() << endl;
	if (cin.rdstate() == ios::goodbit)
		cout << "true" << endl;
	else
		cout << "false" << endl;
	cin >> a;				// 不执行
	cout << a << endl;
	cin.clear();			// 清除错误标位
	cin.sync();				// 清空流
	cin >> a;
	cout << a << endl; 
}

出错后,统统不执行之后的io操作,因为这些操作都会有个if语句先判断这些错误位,如果被置为就直接返回了。出错后可使用clear方法清除错误位,继续输入输出。

ios::goodbit 000 正常

ios::badbit 001 输入或输出流出现异常,不可挽回,一般是系统底层或硬件出错

ios::eofbit 010 已到达文件尾

ios::failbit 100 输入或输出流出现异常,可挽回,一般时其他软件出错

1.4 string类型

#include <string> // 不是`string.h`,`string.h`时C字符串文件
std::string str;

string类具备构造函数和析构函数。

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

int str_main() {

	string str;
	str = "Hello,world!";
	char cstr[] = "abcde";
	string s1(str);			// 复制字符串给s1
	cout << s1 << endl;		// Hello, world!
	string s2(str, 6);		// 从6号位置开始复制
	cout << s2 << endl;		// world!
	string s3(str, 6, 3);	// 从6号位置复制3个
	cout << s3 << endl;		// wor
	string s4(cstr);		// c字符串作为s4的初值
	cout << s4 << endl;		// abcde
	string s5(cstr, 3);		// c字符串第3位起作为s4的初值
	cout << s5 << endl;		// abc
	string s6(5, 'A');		// 5个A作为s6初值
	cout << s6 << endl;		// AAAAA
	string s7(str.begin(), str.begin() + 5);	// 从开头5个字符作为s7初值
	cout << s7 << endl;		// Hello

	return 0;
}

string可使用>= <= > < != 比较字符串,可使用++=操作符连接字符串,可使用[]获取特定字符。

使用迭代器std::string::iterator遍历整个字符串

string str="Hello,world!";
for (string::iterator it = str.begin(); it != str.end(); it++) {
	cout << *it;
}

string特性

string str="Hello,world!";

int s_capacity = str.capacity();	// 返回当前容量(即str中不必增加内存即可存放元素的个数)
int s_max_size = str.max_size();	// 返回可存放的最大字符串的长度
int s_size = str.size();			// 返回当前字符串的大小
int s_len = str.length();			// 返回当前字符串的长度
bool s_empty = str.empty();			// 当前字符串是否为空
str.resize(20, 'c');				// void resize(int len, char c)把当前字符串大小置为20,多去少补,使用'c'进行补充

string查找

size_type find(const basic_string &str,size_type index) 			// 从index开始查找,返回str在字符串中第一次出现的位置,若没有则返回string::npos
size_type find(const char *str,size_type index) 					// 从index开始查找,返回str在字符串中第一次出现的位置,若没有则返回string::npos
size_type find(const char *str,size_type index,size_type length)	// 从index开始查找,查找长度位length,返回str在字符串中第一次出现的位置,若没有则返回string::npos
size_type find(char ch,size_type index) 							// 从index开始查找,返回字符 ch 在字符串中第一次出现的位置,若没有则返回string::npos
    
size_type rfind(const basic_string &str,size_type index) 			// 从index开始查找,返回字符 ch 在字符串中第一次出现的位置,若没有则返回string::npos
size_type find_first_of(char ch,size_type index) 					// 从index开始查找,返回字符 ch 在字符串中第一次出现的位置,若没有则返回string::npos
size_type find_first_not_of(char ch,size_type index) 				// 从index开始查找,返回字符 ch 在字符串中第一次出现的位置,若没有则返回string::npos
string &insert()
string &replace()
string &erase()
string substr()
void swap()
string &append()
void push_back(char c)
const char* data()const;
const char* c_str()const;

1.5 new/delete基本用法

newdelete一般用法

new 类型 [初值]
delete [] 指针变量

newdeletemalloc free区别

1)对于非内部数据类型的对象而言,光用malloc free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc free是库函数而不是运算符,不在编译器的控制权限之内,无法执行构造和析构函数。而new 能完成动态内存分配和初始化工作,delete能够清理和释放内存。2)new出来的指针是直接带类型信息的,而malloc返回的是void指针。

new和多维数组

int (*p)[3][10] = new int[5][3][10];
new出了一个三维数组,去掉最左边的0号维度 [5],还有int [3][10],所以返回的是一个指向二维数组int[3][10]这种类型的指针int(*)[3][10]
try{
	int *p=new int(10);
}
catch (const bad_alloc& e){
	return -1;
}

double *ptr = new(nothrow) double[100000]; // 使用std::nothrow不抛出异常,如果内存分配失败,p的值将为空(0)
if(!ptr){
    cout<<"malloc error"<< endl;
   	return 1;
}
delete []ptr;
cout << "malloc "<< endl;

栈:编译器在需要的时候分配,不需要时自动清除的变量的存储区

堆:由new分配的内存块,它们的释放编译器不去管,由应用程序去控制,一个new要对应一个delete

区别:1)栈由编译器自动管理,无需手工控制;堆的释放工作由程序员控制,容易产生内存碎片。2)计算机会在底层对栈提供专门的寄存器存放栈的地址,而且入栈和出栈都有专门的指令执行,栈的效率较高;堆则是由C/C++提供的,它的机制相对复杂,分配内存时会在堆内存中进行搜索,若没有足够大小的空间则可能调用系统功能去增加程序数据段的内存空间。3)栈内存从高地址向低地址增长;堆内存从低地址向高地址增长。4)32位系统下,堆内存可达到4GB,几乎用不完;而栈一般时有一定大小空间的;但不管栈还是堆都要防止越界现象的发生。

1.6 异常处理

#include <iostream>
#include <string>
using namespace std;
void f1();
void f2();
void f3();
int error_main();

void f3() {
	double a = 0.;
	try {
		throw a;
	}
	catch (float)
	{
		cout << "ok float " << endl;
	}
	cout << "end 3" << endl;
}

void f2() {
	try {
		f3();
	}
	catch (int)
	{
		cout << "ok int " << endl;
	}
	cout << "end int" << endl;
}


void f1() {
	try {
		f2();
	}
	catch (char)
	{
		cout << "ok char " << endl;
	}
	cout << "end char" << endl;
}


int main(){
	try{
		f1();
	}catch(double)
    {
        cout << "ok 0 " << endl;
    }
    cout << "end main" << endl;
    return 0;
}
/*output:
ok 0
end main
*/