C++入门

目录

前言

一命名空间

1原因

2定义

3使用

二缺省参数

1概念

2分类

三函数重载

1概念

2原因

四引用

1语法

 2特性

3使用

4指针与引用 

五内联函数

1概念

2特性

3宏

六auto关键字

1概念

2使用

3常见错误

七指针空值(nullptr)

最后 


前言

C++是建立在C语言的基础之上的语言,不仅解决C语言中的一些缺陷,还增加了面向对象的编程思想和许多有用的库。C++的出现不是来替代C语言,而是C语言的“升级版”。

一命名空间

1原因

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存
在于全局作用域中,可能会导致很多冲突。如:定义的变量与函数名相同

#include <stdio.h>
#include <stdlib.h>
int rand = 10;

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

2定义

C++引进了命名空间的概念。一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。(命名空间就好像张大爷家的菜地,与外界分隔开来)

3使用

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

// 命名空间中可以定义变量/函数/类型
namespace bit
{
int a = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}

访问命名空间的成员有以下几种方式:

1用::(作用域限定符),前面加命名空间的名字(注意:如果前面是空格,默认访问的是全局域)

(指定张大爷家菜地(bit)的摘白菜(a)来食用)

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

2用using和作用限定符,将命名空间的成员引入

(指定从张大爷家的菜地(bit)的黄瓜(b)来食用)

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

3用using newspace 命名空间的名字引入

(使用张大爷家的菜地(bit)中的任何蔬菜来食用)

using namespce bit;
int main()
{
printf("%d\n", b);
Add(10, 20);
return 0;
}

二缺省参数

1概念

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

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

2分类

1全缺省

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

2半缺省

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

注意:1半缺省从右到左连续给值,中间不能隔开 ,因为是语法规定死的。

2缺省参数不能在函数声明和定义中同时出现。会引发歧义,编译器会不知道用哪个缺省参数来使用(报错:重定义默认参数)。

3缺省参数必须是常量或者局部变量。

下面给出缺省参数的一个应用场景:栈的初始化开多少空间的问题

struct Stack
{
	int* a;
	int size;
	int capacity;
};

void StackInit(struct Stack* ps, int n = 4);//在函数的声明中使用缺省参数


void StackInit(struct Stack* ps, int n)//而在函数的定义不用再重复定义缺省参数
{
	ps->a = NULL;
	ps->a = (int*)malloc(sizeof(int) * n);
    //...
}

int main()
{
    struct Stack s1;
    //确定要插入100个数据
	StackInit(&st1, 100);  

	//不知道要插入多少个数据就不传值
	struct Stack st2;
	StackInit(&st2);
    return 0;
}

三函数重载

1概念

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

//1、参数类型不同
int Add(int left, int right);
double Add(double left, double right);

//2、参数个数不同
void f();
void f(int a);

//3、参数类型顺序不同
void f(int a, char b);
void f(char b, int a);

2原因

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

在最后一步的链接中,编译器要通过函数名来找到函数地址进行最后的合并。在C语言中为了防止发生函数名相同而找不到函数地址,所以不允许出现同名函数。

但在日常做项目时,不可避免会发生同名函数出现的问题(毕竟一个项目不一定时同个人完成的)。为了解决这个问题,在C++中,出现了函数重载这一概念。但C++对于函数名的修饰规则没有明确的规定。在不同的编译器中修饰规则大不相同。

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

56edc25313b1419eaada4a40ecbd6273.png

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

四引用

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

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

1语法

类型& 引用变量名(对象名) = 引用实体

void TestRef()
{
    int a = 10;
    int& ra = a;//<====定义引用类型
}

引用与实体的类型必须得相同!!

 2特性

1引用在定义是必须初始化

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

3引用一旦引用一个实体,再不能引用其他实体

int main()
{
    int a=0;
    int& b;//编译报错
    int&c=a;
    int&d=a;
    int e=2;
    c=e;//不是对e的引用,而是赋值
    return 0;

3使用

a作参数:

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

//与指针作对比
void Swap(int* left, int* right)
{
int temp = *left;
*left = *right;
*right = temp;
}

b作返回值:

73aacbf86b0a4684b6c37fb45911e15d.png

d3503dfd92a846b1990b7bdd14df358a.png

fuc()函数返回的类型是int&,但变量a在出函数后a的空间销毁,返回的实际上是a的地址

但vs在处理时把a的空间保留了下来。

与以下代码作对比: 

6144dd8486324ef3a50bf6bad9bb44c2.png

Fuc函数返回的类型时int,int a出函数之前将数据拷贝到寄存器中拷贝给ret

56463ae2f72c42808034cf797bb6a717.png

b15e0f6e21364f7091a0ad0f0addd159.png

总结:返回变量出来函数要摧毁局部变量,返回值的类型不能是引用返回

           全局变量,静态变量,堆上变量(如:malloc)得变量就可以引用返回 

 c传值调用VS传引用调用

#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
void TestRefAndValue()
{
    A a;
// 以值作为函数参数
    size_t begin1 = clock();
   for (size_t i = 0; i < 10000; ++i)
   TestFunc1(a);
   size_t end1 = clock();
// 以引用作为函数参数
   size_t begin2 = clock();
   for (size_t i = 0; i < 10000; ++i)
   TestFunc2(a);
   size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
   cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
   cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

1eae2240e6aa40afacdc7463a3772b97.png

传引用调用不开栈帧直接展开调用,提高效率减少拷贝

4指针与引用 

引用在语法上是不开空间的,与引用的对象共用一块空间。

而指针要开空间存储地址。

底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

0045412e0adb47c680e60e266c3a5848.png

引用和指针的比较:
1. 引用不开空间,指针开空间
2. 引用在定义时必须初始化,指针没有要求
3. 引用不能改变指向,而指针可以
4. 引用相对更安全,指针有野指针
6.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
7.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

8. 有多级指针,但是没有多级引用
9. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

5常引用

int a=10;
const int& b=a;

用const来修饰int&,可以:使用引用权限缩小编译器时支持的 

但反过来行吗?

const int a=10;
//int& b=a;
int c=20,d=30;
//int& e=c+d;
const int& e=c+d;
double f=1.1;
//int& g=f;
const int& g=f;

不行!不允许权限放大 

那么,表达式可不可以? 也是不行,表达式(类型转换)返回的是临时变量(临时变量具有常性)

函数传参也是一样的: 

void fuc(int& b)
int main()
{
    int a=10;
    fuc(a);//可以
    fuc2(10);//不可以,权限放大
}

五内联函数

1概念

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

34ea4742b09d4915903e074ff4043d79.png
 

与不加inline作对比: 

ad058807e83942fd96dffb8d6d5597f6.png

VS下:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下编译器默认不
会对代码进行优化,以下给出设置方式)

087197e6ede9467a93e8dddcd0965405.png

2特性

1.inline是一种以空间(目标文件的大小)换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、而是频繁调用的函数采用inline修饰。否则即使有inline,编译器也不展开!

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

所以:函数重定义的解决问题:

大函数:1函数声明与定义分开 2static修饰(改变链接属性,仅在当前文件可见)

小函数:inline修饰

3宏

在C语言中,我们要解决频繁调用的小函数,提高效率,用宏来实现。

#include<stdio.h>
#define Add(a, b) ((a)+(b))

int main()
{
    int ret=Add(1,2);
    printf("%d",ret);
}

在用宏来定义函数,坑很多(函数参数不用加类型,不用return返回等等) ,也不方便阅读。

但这个恰恰可以用来证明C语言学的扎不扎实的体现:

【面试题】
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。

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

C++有哪些技术替代宏?
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数

六auto关键字

1概念

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

int TestAuto()
{
    return 10;
}
int main()
{
    int a = 10;
    auto b = a;
    auto c = 'a';
    auto d = TestAuto();
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    return 0;
}

注意:使用auto定义变量时必须对其进行初始化

2使用

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 TestAuto()
{
    auto a = 1, b = 2;
    auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

3常见错误

auto不能作为函数的参数:


void TestAuto(auto a)
{
    retrun a;
}
//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导

auto不能直接用来声明数组

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

嵌套使用变量慎用auto

....
auto f2()
{
	auto ret = f3();
	return ret;
}

auto f1()
{
	auto x = f2();
	return x;
}

auto TestAuto()
{
	auto a = f1();
	return a;
}

如上所示,要想直到a的类型是什么,要根据变量往上进行推导。如果一个函数有几百行代码量,要想直到a的变量时不容易的... 

七指针空值(nullptr)


#include<iostream>
using namespace std;
void f(int i)
{
	cout << "f(int)" << endl;
}

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


int main()
{
	f(0);
	f(NULL);
	return 0;
}

 5d01b86a104440649a6d9e81c225b129.png

在上面的代码中,程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但为什么会出现这样呢?

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

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

可以看到,NULL可以是字面常量0,也可以是无类型指针(void*)的常量。 

在上面的代码中,编译器把NULL看成时常量0了。

改进:

void f(int)
{
    cout<<"f(int)"<<endl;
}
void f(int*)
{
    cout<<"f(int*)"<<endl;
}
int main()
{ 
   f(0);
   f((int*)NULL);//强制的类型由函数的参数决定
   f(nullptr);//或者直接使用指针空值
   return 0;
}

注意:
1. 在使用nullptr表示指针空值时不需要包含头文件,因为nullptr是C++11作为新关键字引的。

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

最后 

C++入门要学习的语法大概就是以上的内容,使用的注意事项要理解掌握,成为学习C++的一块“入门砖”!

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值