C++:基础篇

本文详细介绍了C++的关键特性,如命名空间用于避免命名冲突,缺省参数让函数具有默认值,函数重载支持多种参数形式,引用是已存在变量的别名,内联函数提升效率,auto关键字简化类型推导,范围for简化数组遍历,以及nullptr的使用。
摘要由CSDN通过智能技术生成

目录

C++简介

基本概念

命名空间

命名空间定义

命名空间使用

输入输出

缺省参数

缺省参数概念

函数重载

引用

引用概念

引用特性

引用使用场景

引用与指针的区别

内联函数

auto关键字

范围for

nullptr


C++简介

基本概念

C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等。

C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计。

面向对象程序设计是一种新的程序设计范型。程序设计范型是指设计程序的规范、模型和风格,它是一类程序设计语言的基础。

C++拥有计算机运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。

C++的语法和C语言类似,但也有一些新增的语法和特性。例如,C++引入了类和对象的概念,可以通过定义类来封装数据和方法。此外,C++还支持函数重载、运算符重载、异常处理等特性。


命名空间

#include <stdio.h>
#include <stdlib.h>

int rand = 5;

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

在C/C++中,变量或者函数还有类,这些名称都存在于全局作用域当中,肯会导致命名冲突,使用命名空间的目的就是对标识符的名称进行本地化,避免命名冲突或名字污染,namespace关键字的出现就可以解决这种问题的存在。


命名空间定义

namespace d1 
{
    int rand = 10;

 int Add(int left, int right)
 {
    return left + right;
 }

 struct Node
 {
    struct Node* next;
    int val;
 };
}

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字(这里选择叫d1)。命名空间里的内容不仅仅可以存放变量,还可以在里面定义函数,结构体和类。


命名空间使用

namespace d1
{
    int a = 0;
    int b = 1;

    int Add(int left, int right)
 {
    return left + right;
 }

    struct Node
 {
    struct Node* next;
    int val;
 };
}    
    int main()
{
    printf("%d\n", a);
    return 0;
}

该程序执行打印变量a时,会报错因为a是属于命名空间中的成员,其变量查找的规则有三:

1.先在当前作用域下查找。2.如果当前作用域找不到,就向上级作用域查找。3.直到查找到全局作用域时,如果还没找到,那么编译器就会报错。

命名空间的使用有三种方法:

namespace d1 {
    int a = 10;
}
 
int main()
{
    printf("%d\n", d1::a);
 
    return 0;
}

1.加命名空间名称和作用域限定符“::”

namespace d1 
{
    int a = 10;
}
 
using d1::a;
 
int main()
{
    printf("%d\n", a);
 
    return 0;
}

2.使用using将命名空间中某个成员引入

namespace d1 
{
    int a = 10;
}
 
using namespace d1;
 
int main()
{
    printf("%d\n", a);
 
    return 0;
}

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


输入输出

#include <iostream>
using namespace std;//std是C++标准库的命名空间,C++将标准库的定义实现都放到这个命名空间中

int main()
{
	cout << "hello world" << endl;//endl为换行符
	return 0;
}
  1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。
  2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
  3. <<是流插入运算符,>>是流提取运算符。
  4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入输出可以自动识别变量类型。
  5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识, 这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。后面我们还有有一个章节更深入的学习IO流用法及原理。

早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用+std的方式。

#include <iostream>
using std::cout;
using std::endl;
int main()
{
	int a = 1;
	double b = 1.1;

	cout << a << endl << b << endl;

	return 0;
}

缺省参数

缺省参数概念

void TestFunc(int a = 0) 
{
	cout << a << endl;
}
int main()
{
	TestFunc(); // 没有传参时,使用参数的默认值
	TestFunc(10); // 传参时,使用指定的实参
}

缺省参数是 声明或定义函数时 为函数的 参数指定一个默认值 。在调用该函数时,如果没有指定实参则采用该 默认值,否则使用指定的实参。


全缺省参数

void TestFunc(int a = 10, int b = 20, int c = 30) 
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

全缺省参数即所有的参数都赋予了初始值,哪怕一个参数都不传,也可以调用函数

半缺省参数

void TestFunc(int a, int b = 10, int c = 20) 
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

半缺省参数是指,缺省参数时,有一些不被赋予初始值,必须传入值。需要注意的是半缺省参数必须从右向左依次来出,不能间隔着给。缺省参数不能在函数声明和定义中同时出现


函数重载

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

#include <iostream>
using namespace std;
 
int Add(int x, int y) {
	cout << "int x, int y" << endl;        // 为了方便区分
	return x + y;
}
double Add(double x, double y) {
	cout << "double x, double y" << endl;  // 为了方便区分
	return x + y;
}
void Func(int a) {
	cout << "Func(int a)" << endl;
}
void Func(char b, int a) {
	cout << "Func(char b, int a)" << endl;
}
void Func(int a, char b) {
	cout << "int a, char b" << endl;
}
 
 
int main(void)
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;
 
	Func(10);
	Func('A', 20);
	Func(10, 'A');
 
	return 0;
}

需要注意:1.返回值不同,调用时无法区分,或者存在歧义。2.可构成重载但存在歧义


引用

引用概念

引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

引用可以被看作是一个已存在变量的别名,引用和被引用的变量始终指向同一块内存空间,对引用的操作实际上就是对被引用变量的操作。

void d1()
{
    int a = 10;
    int& ra = a;
    printf("%p\n", &a);
    printf("%p\n", &ra);
}

需要注意的是,引用语法概念上不同于指针,它不能执行空值或者没有初始化的变量,因此在定义引用时必须保证所引用的变量已经存在,并且在定义引用时必须进行初始化。


引用特性

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用了一个实体,就不能再引用其他实体
int main()
{
	int a = 10;
	int& b;
	//正确写法:int& b = a;
	return 0;
}

对于 const 修饰的变量意味着只能读不能写,因此在为 const 变量取别名时也要加 const,不能出现本来无法修改,引用过后就能修改的情况。

void test()
{
    const int a = 10;
    int& ra = a;   // 该语句编译时会出错,a为常量
    //正确写法:const int& ra = a;
    
    const int& ra = a;
    int& b = 10; // 该语句编译时会出错,b为常量
    //正确写法:const int& b = 10;
    
    const int& b = 10;
    double d = 12.34;
    int& rd = d; // 该语句编译时会出错,类型不同
    //正确写法:const int& rd = d;
}

而对于最后一个类型不一样的,为什么加了 const 就可以了呢?这是因为在进行隐式类型转化时,会先生成一个临时的 int 常量,先将 d 的值由 double 类型转为 int 类型赋给临时变量,然后再为临时变量取的别名。因为是常量无法修改,所以在取别名时也需要加 const。


引用使用场景

C++中的按引用传递是一种参数传递方式,它允许函数通过引用来操作调用者提供的实参。

按引用传递是将实参的引用传递给形参。

按引用传递的语法是在函数的参数前加上&符号。例如,以下的函数原型中使用了按引用传递

void Swap(int& left, int& right)
{
   int temp = left;
   left = right;
   right = temp;
}

按引用传递是一种高效且灵活的参数传递方式,可以减少内存的复制操作,实现对实参的修改。在C++中,通过引用传递可以提高程序的效率和可读性。


作返回值。引用返回效率会更快一点,因为直接返回的是变量的别名。在正常的函数中,因此函数在调用完就被销毁了,因此它是将返回值先给了一个临时变量,由临时变量返回给主函数。因此需要注意的是,引用返回时返回的必须是静态变量或者是堆上的变量。

int& Count()
{
   static int n = 0;
   n++;
   // ...
   return n;
}

返回引用时,被返回的变量应该仍然存在,否则返回的引用就会变成悬空引用,可能导致不可预期的行为。此外,如果返回引用指向了一个局部变量,函数返回后该变量将被销毁,返回的引用将变得无效。因此,返回引用时需要确保引用的有效性。


引用与指针的区别

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同: 引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
  6. 引用自加即用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

内联函数

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

inline int Add(int x, int y)
{
	int z = x + y;
	return z;
}

C语言中宏函数的优缺点:

缺点:

  1. 不方便调试宏。(因为预编译阶段进行了替换)
  2. 导致代码可读性差,可维护性差,容易误用。
  3. 没有类型安全的检查 。

优点:

  1. 没有类型的严格控制。
  2. 针对频繁调用小函数,不需要建立栈帧,提高性能。

这也是宏函数最重要的一点,不会创建栈帧,直接展开。内联函数延续的宏函数的优点,但是又做了许多优化


auto关键字

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

也就是 auto 会自动推到出变量的类型,在对于一些变量类型比较长是会方便的。

int TestAuto()
{
	return 10;
}
int main()
{
	int a = 10;
	auto b = a;//a是整型,编译器推导出变量b的类型是int型
	auto c = 'a';//'a'是字符型,编译器推导出变量c的类型是char类型
	auto d = TestAuto();//TestAuto函数的返回类型是int型,编译器推导出变量d的类型是int型
    const int e = 10;
    auto ie = &e;
 
	cout << typeid(b).name() << endl;//输出结果为int
	cout << typeid(c).name() << endl;//输出结果为char
	cout << typeid(d).name() << endl;//输出结果为int
	cout << typeid(ie).name() << endl;//输出结果为const int *  
 
	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。

int main()
{
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;

    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;

    *a = 20;
    *b = 30;
     c = 40;
    return 0;
}

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

void test_auto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  
    // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
  1. auto不能作为函数的参数。
  2. auto不能直接用来声明数组。
  3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。
  4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

范围for

范围for工作原理:依次取数组array中的元素,然后将其赋值给临时变量e(但是e的改变不会改变数组,除非使用引用),然后达到了遍历的目的。

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

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
    //下面的auto是元素的类型,我们也可以将其写为int,因为数组元素都是int类型
	for (auto& e : array)//必须使用引用才能拷贝,因为e只是array数组中元素的临时拷贝
		e *= 2;
 
	for (auto e : array)
		cout << e << endl;
}
int main()
{
	TestFor();
	return 0;
}

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

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

nullptr

在C++11标准中,引入了nullptr关键字来表示空指针。C++推荐使用nullptr而不是使用传统的NULL宏定义。

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

在C语言中,我们都是用 NULL 来给指针赋值空的。

不论采取何种定义,在使用空值和指针时,都不可避免地会遇到一些麻烦,比如:

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

需要注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

所以在C++中,定义空指针最好用nullptr。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值