1. C++语法知识点

1.1. 基础语法

  1. #include 指令:引入头文件。
  2. using namespace std; : 允许直接使用 std 命名空间中的成员。
  3. main() 函数:程序的入口。
  4. return 语句:从函数中返回,并可返回一个值给调用者。
  5. 分号 :语句结束符。
  6. 注释:// 单行注释,/*……*/多行注释。
  7. 变量定义与声明:int x;(声明并定义),extern int x;(仅声明)。
  8. 基本数据类型:int, char, float, double, bool, void。
  9. 字面值:整数(123),浮点数(3.14),字符(‘a’),字符串(“hello”),布尔(true,false)。
  10. 算数运算符:+, -, *, /, %(取余)。
  11. 赋值运算符 :=, +=, -=, *=, /=, %=。
  12. 自增自减运算符:++x, x++, --x, x–。
  13. 关系运算符:==, !=, >,<,<=,>=。
  14. 逻辑运算符:&&(与),||(或),!(非)。
  15. 条件运算符(三元运算符):condition ? expr1 : expr2; 。
  16. if-else 语句:条件判断。
  17. switch-case 语句:多分支选择。
  18. while 循环:先判断条件再执行。
  19. do-while 循环:先执行一次再判断。
  20. for 循环:初始化、条件判断、更新表达式一体。

2. C++的第一个程序

C++兼容C语⾔绝⼤多数的语法,所以C语⾔实现的helloworld依旧可以运⾏,C++中需要把定义⽂件代码后缀改为.cpp,vs编译器看到是.cpp就会调⽤C++编译器编译。

//test.cpp
#include<stdio.h>
int main()
{
	printf("hello world!\n");
	
	return 0;
 } 

当然c++也有一套自己的输入输出,严格来说c++版本的helloworld应该这样写。

//test.cpp
#include<iostream>
using namespace std;
int main()
{
	cout<<"hello world!"<<endl;
	
	return 0;
}

这里的 std,cout 我们可能还看不懂,但是没关系,下面会讲到。

3. 作用域运算符

在 C++ 或 C语言中,如果有两个同名变量,即全局变量和局部变量,则局部变量在其作用域内具有优先权。

示例1:

int T = 10;
int main()
{
	int T;
	//……
	T = 5;//此处修改的是局部变量T的值,而不是全局变量T
	return 0;
}

如果需要在局部变量作用域内修改全局变量的值,这应使用作用域运算符::”。

示例2:

int T = 10;
int main()
{
	int T;
	//……
	T = 5;//此处修改的是局部变量T的值
	::T = 10;//修改全局变量的值
	return 0;
}

在C++语言中,“::” 的作用有以下三种:
1.全局作用域运算符(:: 名称)
2.类作用域运算符(类 :: 类成员名)
3.命名空间作用域运算符(命名空间 :: 名称)

4. 命名空间

4.1. namespace

在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全局作⽤域中,可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
比如C语言项目中会存在下面问题,而c++中引入namespace就是为了更好的解决这样的问题。

#include<stdio.h>
#include<stdilb.h>
int rand=10;
int main()
{
	// 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”
	printf("%d\n",rand);
	return 0;
	
}

namespace的定义

  • 定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
  • namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下⾯的rand不在冲突了。
  • C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
  • namespace只能定义在全局,当然他还可以嵌套定义。
  • C++标准库都放在⼀个叫std(standard)的命名空间中。

4.2. 命名空间的使用

编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以
下⾯程序会编译报错。所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:
• 指定命名空间访问,项⽬中推荐这种⽅式。
• using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
• 展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。

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

//int main()
//{
//	//编译报错
//	printf("%d\n",a);
//	return 0; 
//}

//指定命名空间访问
//int main()
//{
//	printf("%d\n",N::a);
//	return 0;
//} 

// using将命名空间中某个成员展开
//using N::b;
//int main()
//{
//	printf("%d\n",N::a);
//	printf("%d\n",b);
//	return 0;
//}

//展开命名空间中全部成员
using namespace N;
int main()
{
	printf("%d\n",a);
	printf("%d\n",b);
	return 0;
 } 

std 后面跟的都是变量、函数、类型。

#include<iostream>
#include<algorithm>
#include<list>
//using namespace std;

using std::cout;//变量

int main()
{
	cout << "hello\n" << std::endl;

	int a[10] = { 1,2,3,4 };
	std::list<int>lt;//类型
	std::sort(a, a + 10);//函数
	return 0;
}

5. C++输入&输出

  • < iostream >是InputOutputStream的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。
  • std::cin 是istream类的对象,它主要⾯向窄字符(narrowcharacters(oftypechar))的标准输⼊流。
  • std::cout 是ostream类的对象,它主要⾯向窄字符的标准输出流。
  • std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
  • <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移)。
  • 使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输⼊输出可以⾃动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出。
  • IO流涉及类和对象,运算符重载、继承等很多⾯向对象的知识,这些知识我们还没有讲解,所以这⾥我们只能简单认识⼀下C++IO流的⽤法,后⾯我们会有专⻔的⼀个章节来细节IO流库。
  • cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们。
  • ⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。
  • 这⾥我们没有包含<stdio.h>,也可以使⽤printf和scanf,在包含间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

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

	cout << a << " " << b << " " << c << endl;
	//std::cout << a << " " << b << " " << c << std::endl;

	//scanf("%d%lf", &a, &b);
	//printf("%d %lf\n", a, b);
	
	//可以⾃动识别变量的类型
	cin >> a;
	cin >> b >> c;
	cout << a << endl;
	cout << b << "  " << c << endl;

	return 0;
}
#include<iostream>
using namespace std;
int main()
{
	//在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码
	//可以提高C++IO效率

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

6. 缺省参数

  • 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数叫做默认参数)
  • 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续给缺省值,不能间隔跳跃给缺省值
  • 调用函数时,编译器按从左到右顺序将实参和形参相结合。不能跳跃给实参。
  • 当函数既有原型声明又有定义时,缺省参数只能在原型声明中给缺省值,不能在函数定义中指定。
  • 当重载函数带有缺省参数时,要注意避免二义性。
#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;
}
//半缺省
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()
{
	Func1();//没有传参时,使⽤参数的默认值
	Func1(1);//传参时,使⽤指定的实参
	Func1(1, 2);
	Func1(1, 2, 3);
	Func2(100);
	Func2(100, 200);//从左到右依次给实参
	Func2(100, 200, 300);
	return 0;
}

7. 函数重载

重载函数是指两个或两个以上的函数具有相同的函数名,但其参数类型不一致或者参数个数不同。编译器根据实参和形参的类型及个数进行相应的匹配,自动确定调用哪一个函数,使重载的函数虽然函数名相同,但功能却不完全相同。
重载函数是 C++ 对 C语言的扩展。C语言是不支持同一作用域中出现同名函数和同名变量的。C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。

#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;
}

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

// 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;
}
//返回值不同不能作为重载条件,因为调⽤时也⽆法区分

//void fxx()
//{}
//
//int fxx()
//{
//   return 0;
//}
// 


// 下⾯两个函数构成重载
// f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁
//当重载函数带有缺省参数时,要注意避免二义性。

void f1()
{
	cout << "f()" << endl;
}
void f1(int a = 10)//可以不传参调用,实参会用缺省值
{
	cout << "f(int a)" << endl;
}

int main()
{
	Add(10, 20);
	Add(10.1, 20.2);
	f();
	f(10);
	f(10, 'a');
	f('a', 10);
	return 0;
}

8. 引用

C++对函数的扩展与增强有以下四点:

  • 引用
  • 函数重载
  • 默认参数(缺省参数)
  • 内联函数

引用是其中最重要与最大的一个特点。

8.1. 引用的概念

引用不是定义一个新的变量,而是给已经存在的变量取别名,引用定义格式为 “ 类型名 & 引用名=被引用的对象名称; ”。

例如:

int x;
int& y=x;

引用就像给原来的对象起的一个“绰号”,访问引用时,实际上访问的就是被引用的那个储蓄单元。

使用引用类型方法的例子:

#include<iostream>
using namespace std;
int main()
{
	int i = 0;
	int& j = i;
	cout << "&i=" << &i << ",&j=" << &j << endl;
	cout << "i=" << i << ",j=" << j << endl;
	i++;
	cout << "i=" << i << ",j=" << j << endl;
	j++;
	cout << "i=" << i << ",j=" << j << endl;
	return 0;
}

程序运行结果:
在这里插入图片描述
由此可见,引用 j 和 i 的地址是相同的,即同一个地址空间,当修改其中任一个值都会同时修改另一个。
跟在类型的后面用来定义一个变量,就是引用。

//void swap(int* p1, int* p2)
//{
//	int tmp = *p1;
//	*p1 = *p2;
//	*p2 = tmp;
//}

//引用功能1:做函数形参,修改形参影响实参
void swap(int& p1, int& p2)//p1 p2就是xy的别名
{
	int tmp = p1;
	p1 = p2;
	p2 = tmp;
}

struct A
{
	int a[100];
	int b;
	//……
};

//一个大一点的变量进行传参的时候,用指针
// void func(struct A* aa)
//{}
// 也可以用引用
// 
//引用功能2:做函数参数,减少拷贝,提高效率
void func(struct A& aa)
{ }

int main()
{
	int x = 1;
	int y = 2;
	//swap(&x, &y);
	swap(x, y);

	struct A a;
	//func(&a);
	func(a);
	
	return 0;
}

8.2. 引用的特性

1.当引用被创建时,它必须被初始化(指针则可以在任何时候被初始化)。
2.一旦一个引用被初始化为指向一个对象时,它就不能被改变为对另一个对象的引用(指针可以在任何时候指向另一个对象)。
3.没有NULL引用,引用必须确保是和一个合法的存储单元关联的。
4.一个变量可以有多个引用。


#include<iostream>
using namespace std;
int main()
{
	int a = 10;

	//int& ra;
	// 编译报错:“ra” :必须初始化引⽤

	int& b = a;

	int c = 20;
	b = c;
	// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
    // 这⾥是⼀个赋值

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	return 0;
}

8.3. 引用与指针

C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。
指针通过变量的地址可间接访问变量,而引用通过变量的别名可直接访问变量。

下面举一个对比的例子:

#include<iostream>
using namespace std;
int main()
{
	int i = 0;
	int* p = &i;//指针p指向变量i的地址
	int& c = i;//定义c为变量i的引用
	cout <<"i="<< i << ", *p=" << *p << ", c=" << c << endl;
	(*p)++;//指针p指向的内容(变量i)自增
	cout << "i=" << i << ", *p=" << *p << ", c=" << c << endl;
	c++;//引用c自增就是变量i自增
	cout << "i=" << i << ", *p=" << *p << ", c=" << c << endl;
	return 0;

}

结果:
在这里插入图片描述

例二:数组、指针和引用的使用方法对比

#include<iostream>
using namespace std;
void changpointer1(int** x) {    // 指向指针的指针
    (*x)++;                    // 修改指针的位置
    **x = 1;                     // 修改指针的内容
}
void changpointer2(int*& x) {   // 对指针的引用
    x++;                       // 对引用的指针进行修改
    *x = 2;                      // 对指针的内容进行修改
}

int main() {
    int iData[3] = { 0 };          // 初始化数组,三个元素都为0
    int* p = iData;              // 指针p指向数组首元素iData[0]
    int i;

    // 第一次输出数组初始值
    for (i = 0; i < 3; i++)
        cout << "iData[" << i << "]=" << iData[i] << " ";
    cout << endl;

    changpointer1(&p);         // 传入指针p的地址(二级指针)
    // 第二次输出调用changpointer1后的数组
    for (i = 0; i < 3; i++)
        cout << "iData[" << i << "]=" << iData[i] << " ";
    cout << endl;

    changpointer2(p);          // 传入指针p的引用
    // 第三次输出调用changpointer2后的数组
    for (i = 0; i < 3; i++)
        cout << "iData[" << i << "]=" << iData[i] << " ";
    cout << endl;

    return 0;
}

运行结果:
在这里插入图片描述
两个函数都能达到通过参数传递在函数中改变指针本身的目的,changpointer1 () 是通过指针地址 &p 来实现的,而 changpointer2 () 则是通过引用指针本身p 来实现的。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐