冰冰学习笔记:C++基础语法

欢迎各位大佬光临本文章!!!

还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正。

本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬、帅哥、美女点点支持,您的每一分关心都是我坚持的动力。

我的博客地址:bingbing~bang的博客_CSDN博客https://blog.csdn.net/bingbing_bang?type=blog

我的gitee:冰冰棒 (BingbingSuperEffort) - Gitee.comhttps://gitee.com/BingbingSuperEffort


目录

前言

1、C++关键字

2、命名空间

2.1命名空间定义

2.2命名空间的使用

3、C++的标准输入和输出

4、缺省参数

4.1缺省参数的定义

4.2缺省参数的分类

4.3缺省参数的使用样例

5、函数重载

5.1函数重载的概念

5.2名字修饰

5.3 extern "C"

6、引用

6.1引用的概念

6.2引用特性

6.3使用场景

6.4常引用

6.5引用和指针的区别

7、内联函数

7.1内联函数的概念

7.2内联函数的特性

8、auto关键字

9、基于范围的for循环

9.1范围for循环的使用条件

10、nullptr


前言

从本章开始,冰冰将进入新的章节C++的学习。

1、C++关键字

C++含有63个关键字,其中包含了C语言的关键字。下表为C++关键字汇总。

C++关键字
asmdoifreturn trycontinue
autodoubleinlineshorttypedeffor
booldynamic_castintsignedtypeidpublic
breakelselongsize oftypenamethrow
caseenummutablestatic
union
wchar_t
catch
explicitnamespacestatic_castunsigneddefault
char
export
new
struct
using
friend
class
extern
operator
switch
virtual
register
const
false
private
template
void
true
const_cas
float
protected
this
volatile
while
delete
goto
reinterpret_cast

2、命名空间

         在C/C++中,变量、函数以及类都是大量存在的,这些变量/函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

2.1命名空间定义

        命名空间在定义时需要使用关键字:namespace,后面跟上命名空间的名字,然后用一对大括号将命名成员括起来,如下所示。

namespace lb
{
    int a=0;
    void test()
    {
        printf("hello world!\n");
    }
    namespace N1
    {
        int b=0;
    }
}

        命名空间中的内容既可以定义变量,也可以定义函数,并且命名空间还可以嵌套使用。注意:在同一个工程中含有多个相同名称的命名空间,编辑器在最后会将其合成同一个命名空间。例如,官方标准库的命名空间名为std,如果我们使用std进行了命名空间的命名,那么会将其与官方的命名空间合并在一起。

        一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

2.2命名空间的使用

        那么命名空间定义完毕我们怎么使用呢?例如下面的代码在使用中会发生什么呢?

namespace lb
{
	int a = 0;
    int b = 10;
	void test()
	{
		printf("hello world!\n");
	}
}

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

        我们会发现,当我们运行函数时,编译器会进行报错,因为它找不到变量a。原因在于变量a虽然被定义了,但是定义在了lb的命名空间内,编译器在寻找时,由于没有指定变量a的具体范围,他会默认去全局寻找,找不到就会报错。

正确使用命名空间的方式有三种:

(1)加命名空间名称和作用域限定符

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

        此种方式使用了域作用限定符"::",该操作符左操作数为命名空间的名称,如果为空白则默认为全局域,右操作数为变量名称,也可以是函数引用。 

(2)使用using将命名空间中的成员引入

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

        此种方式为工程中最常用的方式,仅仅展开命名空间中常用的变量或者函数名称,并不会将整个命名空间展开。 

(3)使用using namespace 命名空间名称引入

using namespace lb;
int main()
{
	printf("%d\n", lb::a);
	printf("%d\n", b);
	test();
	return 0;
}

3、C++的标准输入和输出

        在前面的练习代码中,我们虽然使用了C++的一些语法,可是在进行输入输出的时候使用的还是C语言的函数,虽然C++兼容C语言,但是用起来总感觉并不是那么专业。

        C++也具有输入输出函数,这里只进行简单介绍,后期会有详细讲解。C++的输入函数为cin,输出函数为cout,在使用时必须包含头文件<iostream>以及标准命名空间std。

//C++
using std::cout;
using std::cin;
using std::endl;
int main()
{
	int i = 0;
	double d = 23.3l;
	cin >> i >> d;
	cout << i << ' ' << d << endl;
	return 0;
}

//C
int main()
{
	scanf("%d %lf", &i, &d);
	printf("%d %lf\n", i, d);
	return 0;
}

        上面代码为C++和C的标准输入与输出函数的使用,我们发现,在C++中并没有格式的规定,而只有变量以及一些操作符。">>","<<"为流插入与流提取操作符,"endl"则相当于"\n"是一个换行符。使用C++进行输入输出会比C语言更加简单,原因在于C++中不需要数据格式控制,函数会自动识别。但是在进行格式化输出时,C++就没有C语言这么方便,好在C++可以兼容,我们完全可以继续使用C语言进行操作。

4、缺省参数

4.1缺省参数的定义

        什么是缺省参数,缺省参数是声明或定义函数时为函数的参数指定的一个默认值。在调用该函数时,如果制定了实参,则函数使用指定的实参进行调用,如果未指定,则按照默认值进行调用。

void test1(int a = 0)//缺省值--没有传具体参数时,使用缺省参数
{
	cout << a << endl;
}
int main()
{
    test1();//打印0
    test1(1);//打印1
    return 0;
}

4.2缺省参数的分类

        缺省参数分为两种,全缺省参数,半缺省参数。

(1)全缺省参数

        全缺省参数即为函数参数中的所有参数都进行了默认值的设计,在函数调用过程中,如果一个参数都没有给定,将按照默认值进行打印,如果给定一个参数,则将从左到右进行传参,即test2(1),函数调用过程中,将会打印a的值为1,b的值为默认值。注意:test2(,2)为非法行为。

void test2(int a = 10, int b = 20)//全缺省--参数将从左往右给定
{
	cout << a << endl;
	cout << b << endl;
}
int main()
{
	test2();//a=10,b=20
	test2(1);//a=1,b=20
    test2(1,2);//a=1,b=2
	return 0;
}

(2)半缺省参数

        顾名思义,半缺省参数就是只有一部分参数进行了默认值设置,须符合从右到左进行缺省,并且不能有间隔。

void test3(int a, int b = 20, int c = 30)//半缺省--只能从右往左进行连续缺省,不能间隔
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main()
{
    test3();//非法传递
    test3(1);
    test3(1,2);
    return 0;
}

4.3缺省参数的使用样例

        缺省参数有什么用呢?还记得我们在设计栈的初始化函数的时候吗?那时我们在调用初始化函数的时候,会将capacity进行初始空间开辟,初始开辟多少都是我们自己设定的默认值,也就是4个空间,但是如果我知道我使用这个栈需要存储100个数据,那么栈直接给我一次性开辟100个空间将会省略频繁判断是否容量已满而一直开辟空间的操作。这里我们就可以使用缺省函数进行操作。

struct Stack
{
	int* _a;
	int _n;
	int _capacity;
};
void StackInit(struct Stack*ps,int capacity=4)
{
	int* tmp = (int*)malloc(sizeof(int) * capacity);
	if ( tmp == NULL )
	{
		perror("malloc");
		exit(-1);
	}
	ps->_a = tmp;
	ps->_n = 0;
	ps->_capacity = capacity;
}
int main()
{
	struct Stack s1;
	struct Stack s2;
	StackInit(&s1);
	StackInit(&s1,100);
	return 0;
}

        s1在传参的时候并没有进行容量传参,而默认开辟4个空间,s2指定了100个容量的空间,则可以直接开辟100个。

        这里还要注意一点,含有缺省参数的函数只在声明中进行缺省参数设定,函数定义中不进行缺省。缺省值必须是常量或者全局变量,并且C语言的编译器并不支持。

5、函数重载

5.1函数重载的概念

        什么是函数重载呢?函数重载是函数的一种特殊形式,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数的个数,类型,或者顺序)必须不同,常用来处理实现功能类似数据不同的问题。

        例如在C语言中,我们想设计一个函数能够交换两个参数的数值,我们就需要根据不同的参数类型进行不同的Swap函数设计。这样就会出现很多功能相似的不同名函数,调用过程中很麻烦,需要根据不同的参数来调用不同的函数。但是C++支持重载函数,所以我们可以将这种类似的函数设定为相同的函数名,调用过程中直接调用Swap函数即可,不用关心参数类型。

//C语言
void Swapi(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
void Swapd(double* x, double* y)
{
	double tmp = *x;
	*x = *y;
	*y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	double c = 12.4;
	double d = 23.5;
	Swapi(&a, &b);
	Swapd(&c, &d);
	return 0;
}
//C++重载函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
void Swap(double* x, double* y)
{
	double tmp = *x;
	*x = *y;
	*y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	double c = 12.4;
	double d = 23.5;
	Swap(&a, &b);
	Swap(&c, &d);
    return 0;
}

 注意,重载函数规定了参数的不同,但是没有规定返回值的不同。

5.2名字修饰

        那么问题来了,为什么C++支持重载而C语言不支持呢?这时我们就需要了解到C++和C语言的整个编译连接过程。我们知道任何一个可执行程序都需要经过预编译、编译、汇编、链接四个过程,在链接过程中会形成一个符号表,最终符号表会进行汇总,让每个函数名对应其所在的地址,才能进行函数调用。但是C语言中函数名在形成符号表时函数名修饰规则与C++不同,C语言的函数名修饰规则只与函数名称有关,当具有相同函数名的函数出现时,编译器无法进行区分,因此就会形成链接错误。而C++的函数名修饰规则不仅仅和函数名称有关,还和函数参数有关,因此即便函数名相同的函数,其因为参数不同就会形成不同的函数名称,在符号表汇总时会对应不同的地址,因此可以实现函数重载。

5.3 extern "C"

        既然C++和C的函数命名规则不同,那我们能否让C语言生成的程序来调用C++的库,或者用C++来调用C语言生成的库呢?答案是肯定的,二者可以进行互相调用,只不过需要一个特殊的声明。

        我们知道,C++是兼容C语言的,而C语言并不能兼容C++,所以我们只能让C++去按照C的风格进行编译,而不能让C去改变成C++的风格。因此,extern "C" 的用处很简单,意思就是告诉编译器将该函数按照C语言的规则来编译。

        例如我们以前联系的一道括号匹配的练习题(20. 有效的括号 - 力扣(LeetCode)),这里面我们用C语言写的时候就是自己实现了一个栈,假如我们有一个现成的C语言实现的栈静态库StackClib,而我们现在需要使用C++来实现,并且调用这个C语言实现的库,那我们应该怎么做呢?很简单,我们只需要在包含该库的头文件那里使用extern "C"来进行声明即可。

        当我们使用C去调用C++实现的库时,也是需要声明,只不过需要额外注意一点,C语言不认识extern "C" 该命令,我们包含C++库的头文件后,里面必然会调用extern "C"名令,此时就得需要条件编译进行区分。

6、引用

6.1引用的概念

        在学习C语言的时候,我们学习过指针的概念,通过指针我们可以找到变量,来对其进行操作,但是指针使用起来往往比较麻烦,需要对地址进行解引用才能找到相应的变量,并且在面对多级指针时,总是会产生混乱。基于这种情况,C++对其进行了优化封装,引出了“引用”的概念。

        引用并不是重新定义一个新变量,而是给已经存在的变量取了一个别名,编译器不会为其单独开辟空间,它与它引用的变量公用同一块内存空间。例如,水浒传中豹子头林冲,林冲是他的名字,豹子头是他的外号,两个代号都表示同一个人。

定义引用变量使用的还是"&",但与取地址并不同。

int main()
{
    int a=0;
    int* p=&a;//p为a的地址,&a表示取出a的地址
    int& b=a;//b为a的引用,b就是a
    return 0;
}

6.2引用特性

在使用引用时,必须要遵循以下规则:

(1)引用在定义时必须初始化

(2)一个变量可以有多个引用

(3)引用一旦引用一个实体,就不能引用其他实体

int main()
{
	int a = 10;
	int& b = a;
	int& c = a;
	//int& d;//不存在此种写法
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	int x = 20;
	b = x;
	cout << x << endl;
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

6.3使用场景

(1)做参数--输出型参数,大对象传参,提高效率

        引用可以作为参数进行传递,并没有对变量进行一份临时拷贝,而是直接使用该变量的别名对其操作,提高效率。

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

(2)做返回值--输出型返回对象,调用者可以修改返回对象,减少拷贝,提高效率

        做返回值时要特别注意一点,那就是引用变量不能出了作用域后销毁,否则就会越界访问。 

//int& count()//此种引用返回结果没有保障
//{
//	int n = 0;
//	n++;
//	return n;
//}
int& count()//此种引用返回结果正确,n不会销毁
{
	static int n = 0;
	n++;
	return n;
}

 所以,出了函数作用域,返回对象就销毁的一定不能使用引用返回,而是用传值返回。

6.4常引用

        在一些引用场景中,通常会出现const修饰的引用,此种引用便被称作常引用。

int mian()
{
	int a = 0;
	int& b = a;
	//权限不能放大
	const int c = 0;
	//int& d = c;	
	//权限可以缩小
	int e = 0;
	const int& f = e;
	return 0;
}

        使用const修饰后的引用只有可读权限,不能更改,因此一个变量如果本身被const进行修饰,那么引用也需要const修饰,无法对其进行权限放大,但是未被const修饰的变量,其引用也可被修饰,称为权限缩小。

        常引用还有些特殊用处,例如下面的代码:

int main()
{
	int a = 10;
	double& b = a;//错误
	const double& c = a;//正确
	return 0;
}

        为什么加了const就是正确代码呢? 

        原因很简单,int类型的a变量在被double类型的变量引用时,会发生隐式类型转换,但是变化的并不是a变量本身,而是会生成一个临时变量,该临时变量是double类型,并且具有常性,因此b和c引用的都不是a,而是生成的具有常性的临时变量,如果不加const相当于对该临时变量进行了权限放大,当然是编译失败的,因此加上const就可以对其进行引用。所以a即便发生变化,c也不会变化。

6.5引用和指针的区别

        我们会发现,实际上引用和指针很类似,并且引用的操作一般指针都能进行完成。那为什么还要有引用呢?很简单,指针虽更加强大,但是使用起来复杂,危险。引用虽然局限一些,但是方便简单。语言层面上引用没有额外开辟空间,指针需要开辟4字节或者8字节的空间。实际在底层实现上,引用和指针的实现方式一样。

引用与指针的不同点:

(1)引用在定义时必须进行初始化,指针可以不用。

(2)引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

(3)没有NULL引用,但有NULL指针。

(4)在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)

(5)引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

(6)有多级指针,但是没有多级引用

(7)访问实体方式不同,指针需要显式解引用,引用编译器自己处理

(8)引用比指针使用起来相对更安全

7、内联函数

7.1内联函数的概念

        inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

7.2内联函数的特性

        内联函数是C++为弥补C语言的宏而创建的一种函数模式,内联函数兼顾有宏和函数的优点并且摒弃了宏的缺点,C语言的宏在使用时无法调试,可读性差,没有安全类型检查,因此使用内联函数代替,可以方便进行调试,增加可读性。

但是,内联函数并非加上关键字就构成了内联函数,内联函数具有独特的特性。

(1)inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。

(2) inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

(3)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

8、auto关键字

        C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

例如:

auto并不经常用于此种写法,auto一般用于变量类型太长时,让其自动推导。

auto并非什么情况下都能使用,auto在以下场景中不能使用。

(1)auto不能作为函数的参数

int add(auto a,auto b)
{
	return a + b;
}

(2)auto不能直接用来声明数组

void TestAuto()
{
 int a[] = {1,2,3};
 auto b[] = {4,5,6};
}

(3)为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

9、基于范围的for循环

        auto经常和基于范围的for循环进行一起使用,对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中 引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for ( auto& e : arr )
	{
		e++;
	}
	for ( auto e : arr )
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

9.1范围for循环的使用条件

(1)for循环迭代的范围必须是确定的

        对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

注意:以下代码就有问题,因为for的范围不确定

void TestFor(int array[])
{
 for(auto& e : array)
 cout<< e <<endl; 
}

(2)基于范围的for循环中,e是arr数组中每个元素的拷贝,修改e并不会更改arr中的元素,如果要修改,应使用引用操作。

 10、nullptr

        C语言中空指针为NULL,底层实际上是将其定义为0,但是有些场景并不适用,因此C++中nullptr作为关键字成为了新的空指针定义,因为nullptr实际上是(void*)0。 

void f(int) 
{
 cout<<"f(int)"<<endl; 
}
void f(int*)
 {
 cout<<"f(int*)"<<endl; 
}
int main()
{
 f(0);
 f(NULL);
 f((int*)NULL);
 return 0; 
}

因此,在后面进行空指针的引用时,我们都是用nullptr,使用时不需要包含头文件,nullptr为关键字。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bingbing~bang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值