第 1 章 初识 C++

本文全面介绍了C++编程语言,覆盖了C++的发展历程、特点、应用领域,以及基本语法、数据类型、字符串处理、类型转换、内存管理等内容,适合初学者入门。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.1 C++简介

1.1.1 C++的发展史

1.1.2 C++的特点

1.1.3 C++应用领域

1.2 第一个C++程序

1.iostream文件

2.命名空间

匿名空间

3.cin与cout

1.3 C++ 对 C 语言的扩充

1.3.1 bool 类型

1.3.2 C++ 中的类型转换

1.static_cast<>

2.const_cast<>

3.dynamic_cast<>

4.reinterpret_cast<>

1.3.3 C++ 中的字符串——string

1.用string定义字符串

2.用[ ]来访问字符串中的字符

3.直接用“+”运算符将两个string字符串连接

4.可以直接比较两个string字符串是否相等

5.length()和size()函数

6.swap()函数

1.3.4 引用

1.引用的初始化

2.引用作为函数参数

3.引用与const限定符

1.3.5 动态分配内存(new与delete)

1.new 运算符

2.delete 运算符

1.3.6 默认函数

1.3.7 内联函数

1.3.8 重载函数

1.重载与const形参

2.重载和默认参数


1.1 C++简介

1.1.1 C++的发展史

1.C++语言出现的历史背景

2.C++的诞生与发展

1.1.2 C++的特点

1.保持与C兼容

2.支持面向对象的机制

3.可重用性、可扩展性、可靠性和和维护性

4.代码性能高

5.多种设计风格

1.1.3 C++应用领域

1.2 第一个C++程序

#include <iostream>//包含了输入输出头文件
using namespace std;//引用全局命名空间
int main() 
{
	cout << "hello C++" << endl;
	system("pause");
	return 0;
}

1.iostream文件

输入输出文件。为了和C语言分开,C++标准规定不使用后缀.h的头文件,例如C语言中的string.h头文件,C++用string,C语言中的math头文件,C++使用cmath头文件。这不只是形式上的改变,其内容也有所不同。

2.命名空间

是由程序设计者命名的内存区域,可以根据需要指定一些有名字的空间区域,把定义的变量、函数等标识符放在这个空间中,从而与其他实体定义分隔开。

namespace 空间名{......}
namespace A1
{
    int a = 10;
}

当命名空间外的作用域要使用空间内定义的标识符时,有三种方法可以使用:

(1)空间名加上作用域标识符“::”来标识要引用的实体。

cout<<A1::a;

(2)使用using关键字引入要使用的空间变量

using A1::a;
cout<<a;

(3)使用using关键字直接引入要使用的变量所属的空间

using namespace A1;
namespace A1
{
    int a = 10;
}
namespace A2
{
    int a = 20;
}
using namespace A1; //引入A1命名空间
using namespace A2; //引入A2命名空间
cout<<a; //引起编译错误

A1与A2空间都定义了a变量,引用不明确,编译出错。

C++标准库中的所有标识符都被定义于一个名为std的namespace中,所以std又叫做标准命名空间,要使用过其中定义的标识符就要引入std空间。现在的iostream头文件没有定义全局命名空间,如果要使用命名空间中的标识符,就要单独引入std空间。

匿名空间

namespace
{
    int a;
}

编译时,编译器在内部会为这个命名空间生成一个唯一的名字,而且还会为这个匿名的命名空间生成一条using指令。上述代码等效于:

namespace _UNIQUE_NAME
{
    int a;
}
using namespace _UNIQUE_NAME_;

这个匿名的空间具有一个很有用的特性,那就是对于不同的编译单元(cpp文件),“同一个”匿名空间中的对象,会被当做不同的实体。而这个特性和全局的static修饰是一致的,相当于在这个源程序中定义了一个static int a;也就是只能在当前文件中引用a,而外部是不可见的。

由于static不能修饰类型定义,如class,而且static用在不同的地方有不同的含义,很容易造成混淆,所以相对于static,C++更倾向于使用匿名空间。

3.cin与cout

cin与提取运算符“>>”结合使用,用于读入用户输入,以空白(包括空格、回车、TAB)为分隔符。

cout与插入运算符“<<”结合使用,用于打印消息。通常它还会与操纵符endl使用,endl的效果是结束当前行,并将于设备关联的缓冲区(buffer)中的数据刷新到设备中,保证程序所产生的所有输入都被写入输出流,而不是仅停留在内存中。

1.3 C++ 对 C 语言的扩充

1.3.1 bool 类型

在C++中,提供了bool类型来表示真与假,它有两个取值:true(1)和false(0)。

bool b;
b = 3 == 3; //3==3成立,则为true,所以b的值为1
bool isPrime(int x); //此函数用于判断一个整数是否是质数,返回true或false
bool greater(int x, int y) {return x > y;} //判断两个数的大小,返回true或false
bool b = true;
while(b) {......};

指针也可以隐式地转换为bool类型,空指针转换为false,非空指针转换为true。

1.3.2 C++ 中的类型转换

static_cast<new_type>(expression);
const_cast<new_type>(expression);
dynamic_cast<new_type>(expression);
reinterpret_cast<new_type>(expression);

1.static_cast<>

主要用于执行非多态的转换,用于代替C语言中通常的转换操作。

  • 基本数据类型之间的转换,如把int类型转换为char类型等。
  • 把任何类型的表达式转化成void类型。
  • 把空指针转换成目标类型的指针。
  • 用于类层次结构中基类和子类之间指针或引用的转换,在进行上行转换(把子类的指针或引用转换成基类类型)是安全的,但是在进行下行转换(把基类指针或引用转换成子类类型)时,由于没有动态检查,是不安全的。

static_cast<>不能转换掉数据的const、volatile等特性。

2.const_cast<>

const_cast<>在进行类型转换时用来修改类型的const或volatile属性,除了const或volatile修饰之外,原来的数值和数据类型都是不变的。

#include <iostream>
using namespace std;
int main()
{
	int num = 100;
	const int* p1 = &num;
	int* p2 = const_cast<int*>(p1); //将常量指针p1去掉const属性
	*p2 = 200;
	cout << "num = " << num << endl;
	system("pause");
	return 0; 
}

常量指针被转化为非常量指针,并且仍然指向原来的对象。使用static_cast<>操作符则无法达到这个目的。

3.dynamic_cast<>

该操作符用于运行时检查类型转换是否安全,但该类型至少具有一个虚拟方法。它与static_cast具有相同的方法,但dynamic_cast主要用于类层次间的上行和下行转换,以及类之间的交叉转换。在层次间进行上行转换时,它和static_cast效果是一样的,在进行下行转换时,它具有类型检查的功能,比static_cast更安全。

4.reinterpret_cast<>

通常为操作数的位模式提供较低层的重新解释。也就是说,在将一个类型(如int类型)的数据a转换为另一个类型(如double类型)的数据b时仅仅是将a的比特位复制给b,不做数据转换,也不进行类型检查。需要注意的是,reinterpret_cast要转换的new_type类型必须是指针类型引用算数类型。

char c = 'a';
int d = reinterpret_cast<int&>(ch);

1.3.3 C++ 中的字符串——string

1.用string定义字符串

string s1;                   
s1 = "hello C++";            //第一种方式
string s2 = "hello C++";     //第二种方式
string s3("hello C++");      //第三种方式
string s4(6, 'a');           //第四种方式

第四种方式有两个参数,它表示用6个字符“a”去初始化s4,初始化后的s4的值为“aaaaaa”。

2.用[ ]来访问字符串中的字符

#include <iostream>
#include <string> 
using namespace std;
int main()
{
	string s = "hello,C++";
	s[7] = '-';
	s[8] = '-';
	cout << s << endl;
	system("pause");
	return 0;
}

3.直接用“+”运算符将两个string字符串连接

#include <iostream>
#include <string> 
using namespace std;
int main()
{
	string s1, s2;
	cout << "please input two strings:" << endl;
	cin >> s1 >> s2;
	cout << s1 + s2 << endl;
	system("pause");
	return 0;
}

4.可以直接比较两个string字符串是否相等

#include <iostream>
#include <string> 
using namespace std;
int main()
{
	string s1, s2;
	cout << "please input two strings:" << endl;
	cin >> s1 >> s2;
	if (s1 > s2)
		cout << "the first string is greater" << endl;
	else if (s1 < s2)
		cout << "the second string is greater" << endl;
	else
		cout << "two strings is equal" << endl;
	    system("pause");
	return 0;
}

5.length()和size()函数

int length() const;
int size() const;
#include <iostream>
#include <string> 
using namespace std;
int main()
{
	string s = "hello C++";
	cout << "size:" << s.size() << endl;
	cout << "length:" << s.length() << endl;
	system("pause");
	return 0;
}

6.swap()函数

用来交换两个字符串的值。

void swap(string &s);
#include <iostream>
#include <string> 
using namespace std;
int main()
{
	string s1 = "hello C++";
	string s2 = "chuan zhi bo ke";
	cout << "before swap :" << endl;
	cout << "s1:" << s1 << endl << "s2:" << s2 << endl;
	s1.swap(s2);
	cout << "after swap:" << endl;
	cout << "s1:" << s1 << endl << "s2:" << s2 << endl;
	system("pause");
	return 0;
}

1.3.4 引用

1.引用的初始化

数据类型 &引用名 = 变量名;
int a = 10;
int &b = a;

b和a标识的是同一块内存,对a与对b进行操作,都会更改内存中的数据。

  • 引用在定义时必须初始化,如“int &b;”语句是错误的。
  • 引用在初始化时只能绑定左值,不能绑定常量值,如“int &b = 10;”语句是错误的。
  • 引用一旦初始化,其值就不能再更改,既不能再做别的变量的引用,代码如下所示:
int a = 10;
int b = 20;
int &p = a;
p = b; //为p赋值
  • 数组不能定义引用,应为数组是一组数据,无法定义其别名。

引用实际上是隐式的指针,它与指针的区别主要是指针是一种数据类型引用不是,指针可以转换为它所只想的变量的数据类型,如下代码所示。

int a = 10;
int *p = &a;
double *pd = (double*)p;

经过转换后,pd指针就可以指向double类型数据,而引用则必须要和目标变量的数据类型相同,无法进行数据类型转换。相对指针,引用的定义与使用更为简单,与普通变量的操作相同,如下面代码所示。

int a = 10;
int &b = a;
b = 20;

2.引用作为函数参数

C++增加引用的类型,主要的应用就是把它作为函数的参数,以扩充函数传递数据的功能,引用作函数参数时区别于值传递地址传递

#include <iostream>
using namespace std;
void swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}
int main()
{
	int a, b;
	cout << "please input two nums:" << endl;
	cin >> a >> b;
	swap(a, b);
	cout << "swap:" << a << " " << b << endl;
	system("pause");
	return 0;
}

这是一个典型的区分值传递与地址传递的函数,如果是值传递,由于副本机制无法实现两个数据的交换;址传递则可以完成两个数据的交换,但也需要为形参(指针)分配存储单元,在调用时反复使用“*指针名”,且实参传递时要取地址,这样很容易出现错误且程序的可读性也会下降。而引用传递就完全克服了它们的缺点,使用引用就是直接操作变量,简单高效可读性又好。

引用是隐式的指针,但使用引用与使用指针却有着本质的区别:

(1)使用引用类型就不必在swap()很熟中声明形参是指针变量。指针变量要另外开辟内存单元,其内容是地址。而引用变量不是一个独立的变量,不独占内存单元,在例1-9中引用变量a和b的值的数据类型与实参相同,都是整型

(2)在main()函数中调用swap()函数时,实参不必用变量的地址(在变量名的前面加&),而直接用变量名。系统向形参传递的是实参的地址而不是实参的值。

如果不希望函数改变实参传入的值,可以使用const来限定形参,如下

bool isLonger(const string &s1, const string &s2)
{
    return s1.size() > s2.size();
}

3.引用与const限定符

如果想要用常量值去初始化引用,则引用必须用const来修饰,这样的引用我们称之为const引用。

const int &a = 10; //常量初始化const引用
const int a = 10;
const int &b = a; //const对象初始化const引用

非const对象初始化const引用

int a = 10;
const int &b = a; //非const对象初始化const引用

用非const对象初始化const引用,只是不允许通过该引用修改变量值。除此之外,const引用可以用不同类型的变量来初始化const引用,例如

double d = 1.2;
const int &b = d;
double d = 1.2;
const int temp = (int) d;
const int &b = temp;

在这种情况下,b绑定的是一个临时变量。而当非const引用时,如果绑定到临时变量,那么可以通过引用修改临时变量的值,修改一个临时变量的值是没有任何意义的,因此编译器把这种行为定为是非法的,那么用不同类型的变量初始化一个普通引用自然也是非法的。

1.3.5 动态分配内存(new与delete)

1.new 运算符

new 数据类型(初始化列表);

它分配一块存储空间并且指定了类型信息,并根据初始化列表中给出的值进行初始化,是直接可以使用的内存,这个过程程序员常称之为new一个对象。而且new动态创建对象时不必为该对象命名,直接指定数据类型即可。如果申请内存成功,返回一个类型指针;如果内存申请失败,则返回NULL。

char *pc = new char;  //申请一段空间用来存储char类型,内存中没有初始值
int *pi = new int(10);  //申请一段空间存储int类型数据,初始值为10
double *pd = new double();  //申请一段空间存储double类型的数据,默认初始值为0

new也可以用来创建数组对象,其格式如下:

new 数据类型[数组长度];

 使用new创建数组时,后面可以加小括号(),但括号中不可以指定任何初始值,加小括号时由编译器为其提供默认初始值,而不加小括号时不提供任何初始值。

int *pi = new int[10]();
char *pc = new char[10];
double *pd = new double[0]; 

pi所指向的数组中10个元素初始化为0,pc所指向的数组中没有提供初始值。而pd是一个长度为0的double类型数组,C++虽然不允许定义长度为0的数组变量,但明确指出,调用new创建长度为0的数组是合法的,它返回有效的非零指针,但该指针不能进行有效的解引操作,因为它没有指向任何元素,它主要的作用是用于比较运算。

2.delete 运算符

用来释放new出来的内存空间

delete 指针名;

在释放数组对象时要在指针名前加上[ ],其格式如下:

delete []指针名;

如果缺失[ ],编译器在编译时不会报错,但delete只能释放部分空间,因此在程序运行时会出现内存泄露等问题。

#include <iostream>
using namespace std;
int main()
{
	int *pi = new int(10); //new一个int对象,初始化值为10 
	cout << "*pi = " << *pi << endl;
	*pi = 20;			   //通过指针改变变量的值 
	cout << "*pi = " << *pi << endl; 
	char *pc = new char[10];
	for (int i = 0; i < 10; i++)
		pc[i] = i ;
	for (int i = 0; i < 10; i++)
		cout << pc[i] << " ";
	cout << endl;
	delete pi;			   //释放int对象 
	delete []pc;		   //释放数字对象 
	system("pause");
	return 0; 
}

 用完new对象之后一定要用delete释放内存。

1.3.6 默认参数

在定义或声明函数时给形参一个初始值,在调用函数时,如果不传递实参就使用默认参数值。

int _size(int length = 20, int width = 30);
int _size(int length, int width);
#include <iostream>
using namespace std;
void add(int x, int y = 1, int z = 2); //函数声明中有两个形参有默认值 
int main()
{
	add(1);							   //只传递1给形参想x,y、z使用默认形参 
	add(1, 2);						   //传递1给x,2给y,z使用默认值 
	add(1, 2, 3);					   //传递三个参数,不使用默认形参值 
	system("pause"); 
	return 0; 
}
void add(int x, int y, int z)
{
	cout<< x + y + z << endl; 
}

在使用默认参数时有一些规则需要注意。

(1)默认参数只可在函数声明中出现一次,如果没有函数声明,只有函数定义,才可以在函数定义中设定。

(2)默认参数定义的顺序是自右向左,即如果一个参数设定了默认参数,其右边不能再有普通参数。例如下面的代码:

void add1(int x, int y = 1, int z = 2); //正确
void add2(int x, int y = 1, int z);     //错误,默认参数后不能再有普通的形参

(3)默认参数调用时,遵循参数调用,即有参数传入它会先从左向右依次匹配。

(4)默认参数值可以是全局变量、全局常量,甚至可以是一个函数,但不可以是局部变量,因为默认参数的值是在编译时确定的,而局部变量位置与默认值在编译时无法确定。

1.3.7 内联函数

可以在编译时将函数体嵌入到调用处。

inline 返回值类型 函数名(参数列表)
{
    函数体;
}
#include <iostream>
using namespace std;
inline void func() //内联函数
{
	cout << "这是一个内联函数" << endl; 
 } 
int main()
{
	func(); //内联函数调用
	system("pause");
	return 0; 
}

int main()
{
    cout << "这是一个内联函数" << endl;
    system("pause");
    return 0;
}

这样虽然节省了开销,但是又会造成代码膨胀,因此一般都将结构简单语句少的函数定义为内联函数。内联函数中不可以包含复杂的控制语句,需要注意的是递归函数时不可以定义成内联函数的。inline只是建议编译器将函数嵌入到调用处,编译器会根据函数的长度、复杂度等自行决定是否把函数作为内联函数来调用。

1.3.8 重载函数

不同的函数可以有相同的名字,在调用的时候根据参数的不同确定调用哪个函数。

void add(int x, int y);
void add(float x);
double add(double x, double y);
#include <iostream>
using namespace std;
void add(int x, int y)
{
	cout << "int:" << x + y << endl;
}
void add(float x)
{
	cout << "float:" << 10 + x << endl;
}
double add(double x, double y)
{
	return x + y;
}
int main()
{
	add(10.2);						//一个实型参数 
	add(1, 3);						//两个整型参数 
	cout << add(3.2, 2.3) << endl;	//两个实型参数 
	system("pause");
	return 0;
}

重载函数不能用返回值来区分。

1.重载与const形参

void function(int *x);          //普通指针
void function(const int*x);     //常量指针
void function(int &x);          //普通引用
void function(const int &x);    //常引用
#include <iostream>
using namespace std;
void func1(const int *x)	//常量指针
{
	cout << "const int*:" << *x << endl;
 } 	
void func1(int *x)			//普通指针 
{
	cout << "int*:" << *x << endl;
}
void func2(const int &x)
{
	cout << "const int&:" << x << endl; 
}
void func2(int &x)		    //普通引用 
{
	cout << "int&:" << x << endl;
 } 
int main()
{
	const int a = 1;
	int b = 2;
	func1(&a); //常量参数
	func1(&b); //非常量参数
	func2(a); //常量参数
	func2(b); //非常量参数
	system("pause");
	return 0; 
}

如果是顶层的const形参,即const修饰的是指针本身,则无法和另一个没有顶层const的形参区分开来,对于const修饰形参变量,则只是不能在函数内部改变变量的值并不一定要求传进来的变量就得是常量;对于const修饰指针,只是保证这个指针不指向别的变量而已,而不要求他只想的变量是常量。例如下面的函数:

void func1(int x);
void func1(const int x);    //函数func1()重复声明
void func2(int *x);
void func2(int * const x);  //函数func2()重复声明

这两对函数,第二个函数是对第一个函数的重复声明,因为顶层const不影响传入函数的对象,所以无法以顶层const形参来定义重载函数。

2.重载和默认参数

当使用具有默认参数的函数重载形式时须注意防止调用的二义性

int add(int x, int y = 1);
void add(int x);

当使用函数调用语句“add(10);”会产生歧义,因为它既可以调用第一个add()函数也可以调用第二个add()函数,编译器无法确认到底要调用那个重载函数,这就产生了调用的二义性。在使用时要防止这种情况的发生。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值