C++初阶 —— 入门/概念

目录

一,关键字(C++98)

二,命名空间 

命名空间定义

命名空间使用

三,C++输入/输出

四,缺省参数

五,函数重载

六,引用

七,内联函数

八,auto关键字(C++11)

九,基于范围的for循环(C++11)

十,指针空值nullptr(C++11)


一,关键字(C++98)

  • C语言32个关键字;
  • C++98总共63个关键字;

二,命名空间 

        在C/C++,如大量变量、函数、类的名称都存在于全局作用域中,可能会导致很多冲突;使用命名空间(namespace)的目的就是对标识符的名称进行本地化,以避免命名冲突或名字污染;

        std是C++ 标准库中命名空间的名称,其中包含了大部分标准库的函数和类,使用std命名空间来访问其中的类和函数;

命名空间定义

namespace + 命名空间名字 + { }

  • 命名空间可嵌套;
  • 同一个工程可存在多个相同命名空间,编译器最后会合成到同一个命名空间中;
  • 一个命名空间即定义一个新的作用域,其中所有内容都局限于该空间中;
  • VS19编译器,关键字namespace不可在函数内定义命名空间;
//命名空间,N为命名空间名字
namespace N
{
    //可定义变量,也可定义函数
	int a;
	void fun() {};
}
//可嵌套
namespace N
{
	int a;
	void fun() {};
	namespace N
	{
		int a;
		void fun() {};
	}
}
//命名空间可相同,最后会合并
namespace N
{
	int a;
	void fun1() {};
}

namespace N
{
	int b;
	void fun2() {};
}

命名空间使用

  • 使用命名空间成员
    • 命名空间名称 + 作用域限定符("::");
    • 优:不存在命名污染;缺:使用麻烦;
  • 将内部成员全部展开(全部内容,类似全局)
    • using namespace + 命名空间名称
    • 优:使用方便;缺:暴漏自身,容易命名污染;
  • 引入指定成员(指定内容,类似全局)
    • using + 命名空间名称::命名空间成员
//命名空间名称+限定符
int main()
{
	N::a = 2;
    N::N::a = 2;
	return 0;
}
//using namespace + 命名空间名称
using namespace N;
int main()
{
	a = 2;
	b = 2;
	return 0;
}
//using + 命名空间成员
using N::a;
int main()
{
	a = 2;
	return 0;
}

using关键字

  • 引入命名空间所有成员,using namespace N
  • 引入命名空间特定成员(变量或函数),using N::a
  • C++11引入简化类型名称,即创建别名,using name = type
    • 传统C语言使用typedef创建别名;
//声明类模板别名,不可在函数内使用
template<typename T>
using IntVector = std::vector<T>;

//声明实例类(类型)别名
using IntVector = std::vector<int>;
//在类内部声明的别名
template<class T>
class A
{
public:
	using MyInt = int; //类内可以直接使用
private:
	MyInt _i;
	T _var;
};

A<int>::MyInt var; //但在内外使用必须加上所属类作用域

三,C++输入/输出

  • cout 标准输出(控制台);
  • cin 标准输入(键盘);
  • 需包含头文件<iostream>及标准命名空间std
  • endl一行输出结束输出下一行(等价于"\n");
#include <iostream>
int main()
{
	std::cout << "hello world!" << std::endl;
	return 0;
}
//推荐此方式
#include <iostream>
using namespace std;
int main()
{
	cout << "hello world!" << endl;
	return 0;
}
//输入输出
#include <iostream>
using namespace std;
int main()
{
	int a, b;
	cin >> a >> b;
	cout << a << "/" << b << endl;
	return 0;
}

注:

  • 早期标准库没有在std命名空间中实现,只需包含.h的头文件即可,后来为了和C头文件区分及正确使用命名空间,规定头文件不带.h;
  • 使用C++输入输出更方便,无需数据格式控制,自动识别类型,如%d/%c等,浮点数最多输出五位,但类似结构体的连续输出用printf更好;

四,缺省参数

  • 是指声明或定义函数时,为函数的参数指定一个默认值;
  • 在调用函数时,如未指定实参则使用该默认值;
#include <iostream>
using namespace std;
void fun(int a = 0)
{
	cout << a << endl;
}
int main()
{
	fun();
	fun(10);
	return 0;
}

 分类

  • 全缺省参数;
  • 半缺省参数;
//全缺省参数
void fun(int a = 0, int b = 1, int c = 2)
{
	cout << a << b << c << endl;
}
//半缺省参数
void fun(int a , int b = 1, int c = 2)
{
	cout << a << b << c << endl;
}

注:

  • 参数必须从右往左依次给出,不可间隔;
  • 缺省参数不可在函数声明和定义中同时出现,如声明和定义缺省位置默认值不同,编译器无法确定默认值,即使默认值相同也不可,此时建议定义是不加默认值;
  • 缺省值必须是常量或全局变量;
  • C不支持;
//支持全局变量
int ab = 1212;
void fun(int a = ab)
{
	cout << a << endl;
}

五,函数重载

        函数重载是函数的一种特殊情况,C++允许同一个作用域中声明几个功能类似的同名函数,其形参列表(参数个数或类型或顺序)必须不同,常用来实现功能类似数据类型不同的问题;进而保持代码的可读性和维护性;

函数名相同,参数不同;即通过传递的参数不同,调用不同函数;

  • 必须在同一作用域,同一命名空间下;
  • 必须有不同参数列表,即参数个数、类型、顺序不同;
  • 不可仅通过返回值类型不同来区分函数,会被认为是函数重定义,导致编译错误;
    • 语法调用层面,无法区分,严重歧义;
  • 可与默认参数一起使用,即缺省参数时使用默认值;
  • 返回值要匹配返回类型;
//可根据参数不同调用不同函数
int Add(int a, int b) { return a + b; }
double Add(double a, double b) { return a + b; }
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);
	return 0;
}

名字修饰(name mangling)

        运行程序时,需经历:预处理、编译、汇编、链接;在编译后链接前,如a.o目标文件中无Add函数的地址,地址在b.o文件中,链接器会到b.o符号表中查找地址,然后链接;链接时,依据名字(函数名修饰)查找,不同编译器修饰规则不同;如:g++函数名修饰规则,[_Z+函数长度+函数名+类型首字母];

  • C语言不支持重载,因为同名函数无法区分;
  • C++通过函数名修饰规则来区分,修饰出来的名字不一样;

注:(f.h/f.c/test.c)

  • 预处理:头文件的展开、宏替换、条件编译、去掉注释,f.i/test.i;
  • 编译:检查语法,生成汇编代码,f.s/test.s;
  • 汇编:把汇编代码转化为二进制机器码,目标文件f.o/test.o;
  • 链接:链接到一起,生成可执行程序.exe;

extern "C"

        在C++中有时候可能需要将某些函数按照C的风格来编译,在函数前加extern "C";即编译后的函数修饰名,可调用C库;C++编译器,既可识别C++函数名修饰规则,也可识别C的修饰规则(兼容C);如C的项目调用C++的库,可在C++库的函数前添加extern “c”即可;

//按C编译
extern "C" int Add(int a, int b) { return a + b; }

六,引用

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

类型 + & + 引用变量名(即别名) = 引用实体

  • 引用类型必须和引用实体类型一致;
//引用变量
int a = 10;
int& ra = a;

//引用变量
int a = 10;
int* pa = &a;
int*& rpa = pa;

特性

  • 引用在定义时必须初始化
  • 一个变量可以有多个引用(即多个别名);
  • 引用一旦引用一个实体,再也不能引用其他实体(即不能同时引用两个实体);
  • 不可引用常量或字面值;
int& ra; //此时会报错,必须初始化;
//一个变量可以有多个引用(即多个别名)
int a = 10;
int& ra = a;
int& rb = a;
//引用一旦引用一个实体,再也不能引用其他实体(即不能同时引用两个实体)
int a = 10;
int b = 20;
int& ra = a;
int& ra = b;

常引用

  • 可保护形参,形参不会被改变;
  • 即可传变量,也可传常量;
const int a = 10; 
//int& ra = a; //编译时会报错,因为a为常变量,且不可扩大读写权限
const int& ra = a;
//int& ra = 10; //编译时会报错,因为10为常量
const int& ra = 10;
//可缩小读写权限,但不可放大读写权限
int a = 10;
int& ra = a;
const int& rb = a;
//保护形参,避免误改
void PrintStack(const ST& s);
//即可传变量,也可传常量
int Add(const int& a, const int& b)
{
	return a + b;
}
int main()
{
	int a = 10, b = 20;
	Add(a, b);
	Add(1, 2);
	return 0;
}
int a = 3.14;
double b = a; //类型转换中间会产生一个临时变量
//double& ra = a; 
const double& ra = a; //类型转换中间会产生一个临时变量,临时变量具有常性

使用场景

  • 做参数,避免拷贝提高效率(引用传参)、可修改参数(输出型参数);
  • 做返回值;
//做参数
//因为是实参的别名,实际交换的就是原本的参数变量,类似指针;
void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
//C语法
void SListPushBack(SLTNode** phead, int x);
//C++语法
void SListPushBack(SLTNode*& phead, int x);
//传值返回
//c出作用域会销毁,编译器会生成一个临时变量,复制c的值
//临时变量,如比较小通常再寄存器,如比较大会再main中开辟一块空间
int Add(int a, int b)
{
	int c = a + b;
	return c;
}

//传引用返回
//临时引用,int& tmp=c;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

注:如函数返回时,出了函数作用域,返回对象还未还给系统,则可使用引用返回,如已还给系统,则必须传值返回;

传值/传引用效率对比

用值作为参数或返回值,效率低下,尤其是非常大时;

  • 以值作为参数,在传参时函数不会直接传递实参,而是传递实参的拷贝;
  • 以值作为返回值,在返回期间函数不会将变量本身直接返回,也是返回变量的一份临时拷贝;

引用和指针的区别

在语法上,引用是别名无独立空间,和引用实体共用同一块空间;

在实现上,实际是有空间的,因为引用是按照指针方式实现的(汇编代码完全类似);

  • 引用在定义时必须初始化,指针没有要求;
  • 引用初始化后,不能在引用其他实体,而指针可在任何时候指向任何同类型实体;
  • 没有NULL引用,但有NULL指针;
  • sizeof含义不同,引用为引用类型的大小,指针始终是地址空间字节个数(4/8);
  • 自加不同,引用为引用实体加1,指针则为向后偏移一个类型大小;
  • 有多级指针,无多级引用;
  • 指针需显式解引用,引用编译器自动处理;
  • 引用比指针使用相对安全;

七,内联函数

        以inline修饰的函数叫做内联函数,C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率;C语言是利用宏实现的;

特性

  • inline是一种以空间交换时间的做法,省去调用函数的开销,所以代码很长或有循环/递归函数不宜使用做为内联函数;
  • inline对于编译器只是个建议,编译器会自动优化,如定义为inline的函数体内有循环/递归等,编译器会优化忽略掉内联;
  • inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开,就没有函数地址了,链接就会找不到;

宏优点

  • 增强代码复用性;
  • 提高性能; 

宏缺点

  • 语法复杂,容易出错;
  • 无类型安全检查;
  • 无法调试和递归;

C++替代宏

  • const/enum替代宏常量;
  • inline替代宏函数;

八,auto关键字(C++11)

        早期C/C++中,使用auto修饰的变量具有自动生命周期变量(多余,变量默认拥有自动的生命周期),但一直没有人去使用;C++11中,auto不在是一个存储类型标识符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时推导而得;

  • 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型;
  • 因此auto并非是一种类型的声明,而是一个类型声明时的占位符,编译器在编译时会将auto替换为实际变量类型;
//通过右边赋值对象,自动推导变量类型
int a = 10;
auto b = a;

//定义变量时,必须初始化
//auto b;

auto使用细则

  • auto可与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有区别,但用auto声明引用类型时则必须加&;
  • 在同一行定义多个变量,但在同一行声明多个变量时,必须是相同类型,否则编译器报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量;
int a = 10;
int& ra = a;
auto b = a; //int
//推导指针
auto c = &a; //int*
auto* cc = &a; //int*
//推导引用
auto d = ra; //int
auto& dd = a; //int&
//定义多个变量,必须是相同类型
int a;string b;
//auto c = a, d = b;

auto不能推导的场景

  • auto不能作为函数的参数;
  • auto不能直接用来声明数组;
  • 为了避免与C++98中的auto混淆,C++11保留了auto作为类型指示符的用法;
  • auto实际中最常见的优势用法,是跟新式for循环及lambda表达式等进行配合使用;
//auto不能作为函数的参数
void fun(auto a){}
//auto不能直接用来声明数组
auto arr[] = { 1,2,3 };

注:auto关键字详解链接!

九,基于范围的for循环(C++11)

        对于有范围的集合,有程序员来说明循环的范围是多余的,有时还会容易犯错误;因此C++11中引入基于范围的for循环;

语法:for( 用于迭代的变量 :被迭代的范围){ 循环体 }

int main()
{
	int arr[] = { 1,2,3 };
	for (auto& i : arr)
	{
		i = 1;
	}
	for (auto i : arr)
	{
		cout << i << endl;
	}
	return 0;
}

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

范围for使用条件

  • for循环迭代的范围必须是确定的;
  • 迭代的对象要实现++和==操作;

注:范围for详解链接;

十,指针空值nullptr(C++11)

        空指针,不是内存不存在的指针,是内存为第一个字节的编号,一般不使用这个字节数据;空指针一般用来初始化,表示指针指向无效的空间;

C++98中的指针空值

  • NULL实际是一个宏,可能被定义为字面常量0,或无类型指针常量(void*);
//C语言
#define NULL ((void *)0)

//一个指针没有合法的指向时,指定一个空值
//把void*类型赋予p1,其实会隐式转换相应的类型
int* p1 = NULL;
int* p2 = 0;

C++11指针空值nullptr

C++为强类型,无法隐式转换,引入nullptr表示空指针;

  • 不需包含头文件,nullptr是作为新关键字引入的;
  • 用来代替传统C++中的0或NULL表示空指针的做法;
  • sizeof(nullptr)与sizeof((void*)0)所占字节数相同;

优点:

  • nullptr更能体现指针本质,清晰明了;
  • 使用模板时,传递0或NULL给指针参数容易导致问题;
  • 为了提高代码的健壮性,后续表示指针空值时建议最好使用nullptr;
void f(int a){cout << "f(int)" << endl;}
void f(int* a){cout << "f(int*)" << endl;}
int main()
{
	f(0); //f(int)
	f(NULL); //f(int)
	f((int*)NULL); //f(int*)
    f(nullptr); //f(int*)
}
#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

  • 22
    点赞
  • 101
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
在面向对象的编程中,C语言并不直接支持类和抽象的概念。引用中提到,final关键字用来修饰方法,表示该方法不能在子类中被覆盖。而abstract关键字用来修饰抽象方法,表示该方法必须在子类中被实现。然而,在C语言中,没有对应的关键字来实现类和抽象的概念。 相反,C语言通过结构体来模拟类的概念。结构体是一种用户自定义的数据类型,可以包含多个不同类型的数据成员。通过结构体,我们可以将相关的数据和功能组合在一起。然而,C语言中的结构体不支持继承和多态等面向对象的特性。 在C语言中,我们可以使用函数针来模拟抽象类和接口的概念。函数针可以向不同的函数,通过使用函数针,我们可以实现多态性,即在运行时根据函数向的具体函数来执行不同的操作。 综上所述,C语言并不直接支持面向对象中的类和抽象的概念,但可以使用结构体和函数针来实现类似的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [面向对象——类和对象](https://blog.csdn.net/shouyeren_st/article/details/126210622)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [面向对象编程原则(06)——依赖倒转原则](https://blog.csdn.net/lfdfhl/article/details/126673771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值