栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++入门

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++入门

文章目录
  • C++关键字
  • 命名空间
    • 命名空间定义
    • 命名空间的使用
  • C++输入&输出
  • 缺省参数
    • 缺省参数概念
    • 缺省参数分类
  • 函数重载
    • 函数重载概念
    • 名字修饰
    • extern "C"
  • 引用
    • 引用概念
    • 引用特性
    • 常引用
    • 使用场景
      • 1.做参数
      • 2.传值返回
    • 引用和指针的区别
  • 内联函数
    • 概念
    • 特性
  • auto关键字(C++11)
    • 简介
    • 使用细则
    • auto不能推导的场景
  • 基于范围的for循环(C++11)
    • 范围for的语法
    • 范围for的使用条件
  • 指针空值nullptr(C++11)
    • C++98中的指针空值

C++关键字

C++总计63个关键字,C语言32个关键字

asmdoifreturntrycontinue
autodoubleinlineshorttypedeffor
booldynamic_castintsignedtypeidpublic
breakelselongsizeoftypenamethrow
caseenummutablestaticunionwchar_t
catchexplictnamespacestatic_castunsigneddefault
charexternoperatorswitchvirtualregister
constfalseprivatetemplatevoidtrue
const_castfloatprotectedthisvolatilewhile
deletegotoreinterpret_cast
命名空间

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

命名空间定义

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
1. 普通的命名空间

namespace N1 // N1为命名空间的名称
{
 // 命名空间中的内容,可以定义变量/函数/类型
    int a;
    int Add(int left, int right)
    {
        return left + right;
    }
}

2.命名空间可以嵌套

namespace N2
{
    int a;
    int b;
    int Add(int left, int right)
    {
        return left + right;
    }
 
    namespace N3
    {
        int c;
        int d;
    int Sub(int left, int right)
    {
        return left - right;
    }
    }
}

3.同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

命名空间的使用

1.使用using namespace 命名空间名称引入,全部直接展开到全局。

  • 优点:用起来方便
  • 缺点:把自己的定义暴露出去了,导致命名污染
using namespce N;
int main()
{
    printf("%dn", N::a);
    printf("%dn", b);
    Add(10, 20);
    return 0; 
}

2.加命名空间名称及作用域限定符。访问每个命名空间中的东西时,指定命名空间

  • 优点:不存在命名污染
  • 缺点:用起来麻烦,每个都得去指定命名空间
int main()
{
    printf("%dn", N::a);
    return 0; 
}

3.使用using将命名空间中成员引入。把常用的展开,折中了方法一和方法二。

using N::b;
int main()
{
    printf("%dn", N::a);
    printf("%dn", b);
    return 0; 
}

using std::cout; //std是包含C++标准库的命名空间
using std::endl;
C++输入&输出
#include
using namespace std;
int main()
{
    cout<<"Hello world!!!"< 

说明:

  1. 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
    注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因此推荐使用< iostream >+std的方式。
  2. 使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%c
#include
using namespace std;

int main()
{
	int n;
	cin >> n;//>>输入运算符/流提取运算符

	double* a = (double*)malloc(sizeof(double) * n);
	for (int i = 0;i < n;i++)
	{
		cin >> a[i];
	}
	for (int i = 0;i < n;i++)
	{
		cout << a[i] << " ";//<<输出运算符/流插入运算符
	}
	cout << endl;
	cout << 'n';

	return 0;
}
int main()
{
	//自动识别类型
	char ch = 'A';
	int i = 10;
	int* p = &i;
	double d = 1.111111;

	//自动识别变量的类型
	cout << ch << endl;
	cout << i << endl;
	cout << p << endl;
	cout << d << endl;

	//类似下面场景用printf更好用一些
	//C++中,不用纠结到底用哪个,哪个好用用哪个
	struct Student s = { "张三",18 };
	cout << "名字:" << s.name << " " << "年龄:" << s.age << endl;
	printf("名字:%s 年龄:%dn", s.name, s.age);

	printf("%.2fn", d);

	return 0;
}
缺省参数 缺省参数概念

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

void TestFunc(int a = 0)
{
    cout< 
缺省参数分类 

缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
注意:缺省参数不能再函数声明和定义中同时出现。缺省值必须是常量或全局变量。C语言不支持(编译器不支持)
1.全缺省参数

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

int main()
{
	TestFunc();
	TestFunc(1);
	TestFunc(1,2);
	TestFunc(1,2,3);

	return 0;
}

2.半缺省参数
注意:半缺省参数必须从右往左依次来给出,不能间隔着给
如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

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

int main()
{
	TestFunc(1);
	TestFunc(1,2);
	TestFunc(1,2,3);

	return 0;
}
函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”

函数重载概念

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

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

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

int main()
{
	Add(10, 20);
	Add(10.0, 20.0);
	Add(10L, 20L);

    return 0;
}
名字修饰

为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

实际我们的项目通常是由多个头文件和多个源文件构成,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?

所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。

那么链接时,面对Add函数,链接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。

由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,下面我们使用了gcc演示了这个修饰后的名字。

通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。

gcc:

C语言为什么不支持重载?
C编译器直接用函数名关联。函数名相同时,无法区别。

g++:

C++如何支持重载?
函数名修饰规则->不能直接用函数名,要对函数名进行修饰,带入参数特点修饰。函数名相同,只要参数不同,修饰出来的名字就不同,就能区分了,所以支持重载

编译器能不能实现函数名相同参数相同,返回值不同时构成重载呢? 答案是:不能!
比如说 int func(); 和 double func(); 如果把返回值带进修饰规则,比如修饰后分别尾 _Z3ifunc和_Z3dfunc ,那么编译器层面是可以区分的。但语法调用层面带有严重歧义。func();调用时,要调用哪一个?就无法区分。

extern “C”

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决。

C++编译器能识别C++函数名修饰规则,也能识别C的修饰规则,因为C++本来就兼容C。在C++项目中可能会用到一些C实现的库(动态库和静态库),直接就可以调用。C的项目想要调用某些C++的库,加extern “C”,就可以让C++的库按照C的风格编译,就能让C的项目也调用C++的库。

引用 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
类型& 引用变量名(对象名) = 引用实体;

int main()
{
	//注意这里跟C语言取地址用了一个符号 & ,但它们之间没有任何关联
	int a = 10;
	int& b = a;
	int& c = a;
	int& d = b;

	c = 20;
	d = 30;

	return 0;
}

注意: 引用类型必须和引用实体是同种类型的

引用特性
  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
void TestRef()
{
    int a = 10;
    // int& ra; // 该条语句编译时会出错
    int& ra = a;
    int& rra = a;
    printf("%p %p %pn", &a, &ra, &rra); 
}
常引用
void TestConstRef()
{
    const int a = 10;
    //int& ra = a; // 变成你的别名,还能修改你(错误)
    const int& ra = a;
   
    // int& b = 10; // 该语句编译时会出错,b为常量
    const int& b = 10;
    
    double d = 12.34;
    //int& rd = d; // 该语句编译时会出错,类型不同
    const int& rd = d;//变成你的别名,不能修改你(正确)
    //d可以改,rd只能读不能写。并不是每个别名都跟原名字有一样的权限
}
使用场景 1.做参数

以之前的栈为例,如果是静态的:

typedef struct Stack
{
	int a[1000];
	int top;
	int capacity;
}ST;
void StackInit(ST& s)//这里传引用是为了形参的改变,从而影响实参
{
	s.top = 0;
	s.capacity = 1000;
	//...
}
void PrintStack(const ST& s)
{
	//1.传引用是为了减少传值传参时的拷贝
	//2.可以保护形参不被改变
	//假设有这样一个逻辑 -> 如果是const引用,这里就可以被检查出来
	//if(s.capacity=0)//本来应该是==,不小心写成=
	{

	}
}
void func(const int& n)//const引用做参数的第二个好处,既可接收变量,也可接收常量
{

}
int main()
{
	ST st;
	StackInit(st);
	//...
	PrintStack(st);

	int i = 10;
	const int j = 30;
	func(i);
	func(20);
	func(j);

	return 0;
}

引用做参数:

  • 输出型参数。void swap(int& a,int& b)
  • 当参数变量比较大,相对于传值,引用做参数可以较少拷贝。void StackPrint(const Stack& st) ps:如果函数中不改变形参的话,建议用const Type& 好处:1.在函数中保护形参,避免被误改。2.既可传普通对象,还可以传const对象。
2.传值返回

先来看以下代码:

int main()
{
	int i = 10;
	double d = i;
	//double& r = i;  报错
	const double& r = i;

	return 0;
}

为什么double& r = i;会报错?

int Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1,2) is :" << ret << endl;
	return 0;
}

输出结果是不确定的。取决于平台销毁栈帧时是否会清理栈帧空间。释放空间销毁栈帧的意思是那块空间的使用权不属于你了,但这块空间还在。 这里返回的是c的别名的值即3,赋值给ret。如果清理了栈帧空间那么c中就是随机值,如果没有清理那就还是3。运行上面的程序,可以看到vs上没有清理栈帧,结果还是3,但这不代表程序没错。
用内存空间就像是租房子一样,操作系统就是房东,我们申请内存,就是房东把房子给我用,法律保护别人不会到你的房子里面来。释放内存就像是我们退租了,房子还在,但使用权不是我们的了,房东可能把房子继续租给别人。
如果把代码改成int& ret=Add(1,2),那么就是返回的c的别名的别名给ret,而ret直接就是c的别名。

结论:出了func函数的作用域,ret变量会销毁,就不能引用返回。出了func函数的作用域,ret变量不会销毁(全局变量),就可以用引用返回。如以下场景:

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

总结引用返回的意义:

  • 引用返回的价值是减少了拷贝
  • 方便实现类似operatot[]
引用和指针的区别

1.在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。而指针是开辟一块空间,存储变量地址。

int main()
{
    int a = 10;
    int& ra = a;
 
    cout<<"&a = "<<&a< 

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

范围for的使用条件

1.for循环迭代的范围必须是确定的。
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定

void TestFor(int array[])//函数传数组传入的是数组名的地址
{
    for(auto& e : array)
    cout<< e < 

2.迭代的对象要实现++和==的操作。

指针空值nullptr(C++11)

我们声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void f(int)
{
	cout << "f(int)" << endl;
}

void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	// C++98
	int* p1 = NULL;
	int* p2 = 0;
	f(0);
	f(NULL);

	// C++11 以后推荐用它当空指针使用
	int* p3 = nullptr;
	f(0);
	f(nullptr);

	return 0;
}

指针本质是内存按字节为单位空间的编号。空指针并不是不存在的指针,而是内存第一个字节的编号。一般我们不使用这个字节存储有效数据,用空指针一般用来初始化,表示指针指向没有存一块存有有效数据的空间。

C++98中的指针空值

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

void f(int)
{
    cout<<"f(int)"< 

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void * )0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/433525.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号