C++基础精讲-01
1C++概述
1.1初识C++
发展历程:
C++ 由本贾尼・斯特劳斯特卢普在 20 世纪 70 年代开发,它在 C 语言的基础上增加了面向对象编程的特性,最初被称为 “C with Classes”,后来逐渐发展成为独立的 C++ 语言。
语言特点
(1)高效性:C++ 允许直接操作计算机硬件,能够对内存进行精细的管理,具有很高的执行效率,适用于开发对性能要求极高的程序,如游戏、操作系统、嵌入式系统等。
(2)面向对象:支持面向对象编程,将数据和操作数据的方法封装在类中,通过继承和多态性实现代码的复用和扩展,使程序结构更加清晰、易于维护。
(3)兼容性:与 C 语言保持了高度的兼容性,使得 C++ 可以直接使用大量现有的 C 语言代码和库,同时还能享受到 C++ 面向对象和高级特性带来的便利。
(4)丰富的库:拥有标准模板库(STL)等丰富的库,提供了各种常用的数据结构(如向量、链表、映射等)和算法(如排序、搜索等),大大提高了开发效率。
应用领域
C++ 应用广泛,除了上述提到的游戏开发、操作系统和嵌入式系统外,还用于图形图像、数据库管理、网络编程、金融计算等领域。
C和C++的关系
C++ 是 C 语言的超集,它继承了 C 语言的优点,同时又进行了扩展和改进。
g++编译器的下载
g++编译器是用来编译C++的;
sudo apt update
sudo apt install g++
第一个C++程序
#include<iostream> // 包含输入输出流库,使得程序可以使用标准输入输出功能,如 cout 和 cin
using namespace std; // 使用标准命名空间,这样在使用标准库中的对象和函数时就不需要加 std:: 前缀int main(int argc, char* argv[]) // 主函数,程序的入口点,argc 表示命令行参数的数量,argv 是一个指向命令行参数的字符指针数组
{cout << "hello world" << endl; // 输出字符串 "hello world" 到标准输出(通常是控制台),endl 表示换行return 0; // 主函数返回 0,表示程序正常结束
}
2.命名空间
2.1命名空间的概述
1.什么是实体?
变量、常量、函数、结构体、引用、类、对象、模板、命名空间等,它们都称为实体
2.什么是命名空间?
命名空间又称为名字空间,是程序员命名的内存区域,程序员根据需要指定一些有名字的空间域,把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开
比如c++中的输入输出cin和cout就在std标准命名空间中定义;
3.为什么要使用命名空间?
大型工程常由多人分工完成,不同人设计不同头文件,这可能导致不同头文件中类或函数命名相同,引发名字冲突。C++为解决该问题引入命名空间,它是用户自定义的作用域,在不同作用域可定义同名变量且互不干扰,系统能进行区分。
4.命名空间的基本格式
namespace 名
namespace A
{int a=10;......}
2.1命名空间基本使用
1.作用域限定符::
每次要使用某个命名空间中的实体时,都直接加上作用域限定符::
#include<iostream> // 引入输入输出流库,用于使用 cout 和 endl 进行输出操作// 定义命名空间 A
namespace A {int a = 10; // 在命名空间 A 中定义一个整型变量 a 并初始化为 10void fun1(void) { // 在命名空间 A 中定义一个无返回值、无参数的函数 fun1std::cout << "a=" << a << std::endl; // 输出命名空间 A 中变量 a 的值,并换行}
}// 定义一个无返回值、无参数的函数 test1
void test1(void) {A::fun1(); // 调用命名空间 A 中的 fun1 函数,输出 a 的初始值A::a = 100; // 修改命名空间 A 中变量 a 的值为 100A::fun1(); // 再次调用命名空间 A 中的 fun1 函数,输出修改后 a 的值
}// 主函数,程序的入口点
int main(void) {test1(); // 调用 test1 函数return 0; // 主函数正常结束,返回 0
}
好处:准确,只要命名空间中确实有这个实体,就能够准确调用(访问)
坏处:繁琐
2.using编译指令
如第一个C++程序,其中std代表的是标准命名空间。cout和endl都是std中的实体,使用了using编译指令后,这两个实体就可以直接使用了。(把命名空间中的所有实体一次性引入到程序)
#include<iostream>
using namespace std;namespace A
{int a=100;void fun1(void){cout<<"a="<<a<<endl;}
} using namespace A;void test1(void)
{ fun1();a=200;fun1();} int main(void)
{ test1();return 0;
}
~
~
建议1
using编译指令尽量写在局部作用域,这样using编译指令的效果也会在其作用域结束时结束;可以避免命名冲突
错误案例
#include<iostream>
using namespace std;namespace A
{int a=100;} namespace B
{int a=200;
}using namespace A;
using namespace B; //A和B命令空间里的变量a会产生冲突void test1(void)
{ //这里的a不知道访问A还是B里的变量a;a=200;cout<<"a="<<a<<endl;} int main(void)
{ test1();return 0;
}
解决办法
1.使用作用域限定符
2.using编译指令尽量写在局部作用域,避免和其他变量名起冲突
3.using声明机制(推荐)
—— 需要什么就声明什么
建议将using声明语句写在局部作用域中。此时即使命名空间中实体与全局位置实体重名,在局部位置也遵循“就近原则”形成屏蔽
#include<iostream>
using std::cin;
using std::cout;
using std::endl;namespace A
{int a=10;int b=20;
}int a=1000;void test1(void)
{ using A::a;cout<<"a="<<a<<endl;
}int main(int argc,char*argv[])
{ test1();cout<<"a="<<a<<endl;return 0;
}
4.命名空间的嵌套使用
命名空间中还可以定义命名空间
#include<iostream>
using std::cin;
using std::cout;
using std::endl;namespace A
{int a=10;int b=20;namespace B{int c=200;}
}void test1(void)
{ using A::B::c;cout<<"c="<<c<<endl;
}int main(int argc,char*argv[])
{ test1();return 0;
}
5.匿名命名空间
命名空间还可以不定义名字,不定义名字的命名空间称为匿名命名空间(简称匿名空间)。
通常,如果我们希望一部分实体只在本文件中起作用,那么可以将它们定义在匿名空间中。
namespace {
//...
}
使用匿名空间中实体时,可以直接使用,也可以加上作用域限定符(没有空间名)
#include<iostream>
using std::cout;
using std::cin;
using std::endl;namespace
{int a=100;int b=200;int c=300;
}int main(int argc,char*argv[])
{cout<<"a="<<a<<endl;int b=2;cout<<"b="<<b<<endl;cout<<"b="<<::b<<endl;return 0;
}
~
~
匿名空间注意事项:
(1)匿名空间不要定义与全局空间中同名的实体;
(2)匿名空间中的实体不能跨模块调用。
6.命名空间补充
在同一个源文件中可以多次定义同名的命名空间,被认为是同一个命名空间,所以不能在其中定义相同的实体
#include<iostream>
using std::cout;
using std::cin;
using std::endl;namespace A
{int a=100;}namespace A
{int c=200;
}void test(void)
{using A::a;using A::c;cout<<"a="<<a<<endl;cout<<"c="<<c<<endl;
}int main(int argc,char*argv[])
{test();return 0;
}
3.const关键字
3.1修饰内置类型
const修饰的变量称为const常量,之后不能修改其值。(本质还是变量,使用时也是当成变量使用,只是被赋予只读属性)
const常量在定义时必须初始化。
#include<iostream>
using std::cin;
using std::cout;
using std::endl;void test1(void)
{ //只有读权限,没有写权限const int number1 = 10;int const number2 = 20;cout<<number1<<endl;cout<<number2<<endl;}int main(void)
{ test1();return 0;
}
宏定义的方式创建常量
#define MAX 100
3.2修饰指针类型*
1. 指向常量的指针
指向常量的指针意味着不能通过该指针来修改所指向对象的值,不过指针本身可以指向其他对象。
const 数据类型 * 指针名;
#include <iostream>
using std::cin;
using std::cout;
using std::endl;int main(void) {int num = 10;const int *ptr = #// *ptr = 20; // 错误,不能通过指向常量的指针修改所指向的值num = 20; // 可以直接修改 num 的值cout << *ptr << endl; // 输出 20return 0;
}
2. 常量指针
常量指针表示指针本身的值(即所指向的地址)不能被修改,不过可以通过该指针修改所指向对象的值。
数据类型 * const 指针名;
#include <iostream>
using std::cin;
using std::cout;
using std::endl;int main(void) {int num1 = 10;int num2 = 20;int * const ptr = &num1;*ptr = 30; // 可以通过常量指针修改所指向的值// ptr = &num2; // 错误,不能修改常量指针的值cout << *ptr << endl; // 输出 30return 0;
}
3. 指向常量的常量指针
指向常量的常量指针既不能修改指针所指向对象的值,也不能修改指针本身的值。
const 数据类型 * const 指针名;
#include <iostream>
using std::cin;
using std::cout;
using std::endl;int main(void) {int num1 = 10;int num2 = 20;const int * const ptr = &num1;// *ptr = 30; // 错误,不能通过指向常量的常量指针修改所指向的值// ptr = &num2; // 错误,不能修改指向常量的常量指针的值cout << *ptr << endl; // 输出 10return 0;
}
总结
指向常量的指针:不能通过指针修改所指向对象的值,但指针可以指向其他对象。
常量指针:指针本身的值不能修改,但可以通过指针修改所指向对象的值。
指向常量的常量指针:既不能修改指针所指向对象的值,也不能修改指针本身的值。
3.3const 常量和宏定义常量区别
发生时机:
宏定义在 C 语言预处理阶段,单纯进行字符串替换。比如 #define PI 3.14 ,预处理时会把代码中所有 PI 都替换成 3.14 。
const 常量在编译时起作用,它本质近似变量,只是被 const 限定为只读。像 const double pi = 3.14; ,编译时会处理这个定义。
类型和安全检查:
宏定义没有类型概念,不做类型检查。如 #define ADD(a, b) ((a)+(b)) ,传入不同类型参数都直接替换展开,可能引发错误。
const 常量有明确类型,编译期会执行类型检查。如 const int num = 5; ,类型不匹配的赋值等操作编译时会报错 。
通常推荐用 const 常量替代宏定义常量,能利用编译期类型检查机制,减少因宏定义的简单替换带来的错误风险。