【C++】C++基础语法(下)

简介: C++基础语法呢,有非常多的细节,需要大家慢慢来摸索,仔细的回顾,反复的复习


1.定义

函数重载: 是函数的一种特殊情况, C++ 允许在 同一作用域中 声明几个功能类似 的同名函数 ,这

些同名函数的 形参列表 ( 参数个数 或 类型 或 类型顺序 ) 不同 ,常用来处理实现功能类似数据类型

不同的问题。

总之一句话,函数名相同,参数不同。参数不同包括,参数个数,参数类型,参数顺序。

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

顺序不同要注意的是:

void f(int a, char b)
{
  cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
  cout << "f(char b, int a)" << endl;
}
他们属于函数重载,顺序不同本质就是类型不同,与变量名没有关系
void f(int a, int b)
{
  cout << "f(int a,char b)" << endl;
}
void f(int b, int a)
{
  cout << "f(int a,char b)" << endl;
}
这两个就不属于函数重载,类型的顺序还是int,int

那怎么调用呢??函数重载可以支持自动识别类型

2.缺省函数与重载函数

void f()
{
  cout << "f()" << endl;
}
void f(int a=10,int b=100)
{
  cout << "f(int a,int b)" << endl;
}

函数名相同,参数不同就可以构成 函数重载,但在调用时,f()这样调用会报错,发生歧义。

那么,函数重载是怎么进行的呢??

下面会简单的让大家理解这个过程。

在调用函数时,我们会找函数的地址,来调用它

那么如何找到它的地址呢??

就是通过符号表来找到的,在linux编译C++中,它是这样进行的:

函数名都叫  f  所以都是_z1f,第一个函数的参数是int,所以是_z1fi  (int),以此类推,第二个则是i c


,第三个是c i,所以这就很容易的找到了函数名,并找到它的地址,再调用。


那么,就会有这样一个问题,参数不同构成函数重载,那我要返回值不同构成函数重载可以吗??


是因为函数名修饰规则没有带返回值的原因吗??


就是在符号表中函数名这里,再添加不同的返回值所代表的符号不就可以了吗??


当然不行!!

那是因为,我们在调用函数时,只可以指定它的参数,但无法指定他的返回值!!

六、auto自动识别类型

1.定义

auto可以自动识别类型,举例说明:


int a=10;


auto b=a;


//这样就可以自动识别类型,来确定b的类型,当然auto针对的还是较长的类型


比如:


std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", "橙子" },{ "pear", "梨" } };

//这个类型我们现在不需要知道


// auto是方便类型下面的地方

//std::map<std::string, std::string>::iterator it = m.begin();

 auto it = m.begin();  会自动识别it类型,无需重复复杂类型

2.使用规则

 1.与引用结合

用 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;
    return 0; 
}

typeid(变量名),可以拿到变量类型的字符串。结果显示如下:

2.注意:

引用只是起别名,本质上,还是变量本身的类型。


在同一行使用auto推导类型时,只能是相同类型的。

void TestAuto ()

{

   auto a = 1 , b = 2 ;

   auto c = 3 , d = 4.0 ;   // 该行代码会编译失败,因为 c 和 d 的初始化表达式类型不同

}

          auto 不能作为函数的参数,会无法对参数进行类型推导

          auto 不能直接用来声明数组

3. 基于范围的for循环(C++11)


对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因


此 C++11 中引入了基于范围的 for 循环。 for 循环后的括号由冒号 “ : ” 分为两部分:第一部分是范

围内用于迭代的变量,第二部分则表示被迭代的范围 。

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)  //auto 后面的e是可以变得,只不过习惯是e,element
     e *= 2;
for(auto e : array)
     cout << e << " ";
     cout<<endl;
}
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
若将&引用去掉,那么则无法改变数组中的元素,只是改变了存在e中的元素
for(auto e : array)  
     e *= 2;
for(auto e : array)
     cout << e << " ";
}
int main()
{
    TestFor();
    TestFor1();
}

 

引用详细在这里

但对于下面这种情况,就是错误的:


void TestFor ( int array [])

{

   for ( auto & e : array )

       cout << e << endl ;

}

这里的array只是地址,因为传数组时,只能将其首元素就是地址传来。

七、指针空值nullptr(C++11)

在C语言中,指针为空时为NULL;


NULL实际是一个宏,在传统的C头文件(stddef.h)中,


NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何

种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。

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 ;

}  

                                          

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器

默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

所以,在C++中,就重新定义了nullptr,为(void*)类型


注意:

1. 在 C++11 中, sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。

2. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr 。

八、内联函数

在我们编译代码的时候,总会有一些短小的代码,但需要我们反复去调用,那么调用函数就会建立栈帧,但是宏可以解决这样的问题,预先定义好宏,在预处理时,都会被替换直接展开,不需要写函数。


但是宏有几个缺点,不能调试;没有类型安全检测;容易写错!!


所以才会有内联函数来替代宏,让我们更方便。


以 inline 修饰 的函数叫做内联函数, 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数调

用建立栈帧的开销,内联函数提升程序运行的效率。

未加内联:

加了内联:

call是调用函数,加了内联以后,无需建立栈帧。


那么是不是我们以后可以随随便便加内联,都把需要的展开??


首先当然不是,内联针对的是,代码少,但是需要经常调用,而且,你加了内联,只是像编译器说明,发出的一个请求,具体编译器要不要展开,人家自己考虑,可以忽略你这个请求!


比如,有的代码代码很长,如果它有100行代码,要调用100次,那你展开岂不是需要100*100行代码??所以编译器是不会随随便便展开的。


总结:


inline是一种 以空间换时间的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会

用函数体替换函数调用,缺陷:可能会使目标文件变大,(编译好的指令影响的是可执行文件的大小,及安装包的大小)优势:少了调用开销,提高程序运行效率。

2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建

议:将 函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、 不

是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

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

了,链接就会找不到。

只要加了inline内联,就不会生成符号表。

在调用函数的时候,只有声明,没有定义就会链接,去找符号表,但是只要加了inline内联

就不会生成符号表,就会报错。

调用func1时,找不到符号表,直接报错。


所以最好的方式就是,定义和声明在一起,找的时候,会直接在上面的定义中调用。


总结

基础的语法知识细节很多,需要我们去仔细去学习,在后续学习中,这些必要的语法知识是非常重要的!!我们下期再见!

目录
相关文章
|
2月前
|
Java C# C++
C++ 11新特性之语法甜点1
C++ 11新特性之语法甜点1
33 4
|
2月前
|
编译器 C++ 容器
C++ 11新特性之语法甜点2
C++ 11新特性之语法甜点2
30 1
|
2月前
|
存储 算法 编译器
C++ 11新特性之语法甜点4
C++ 11新特性之语法甜点4
28 0
|
2月前
|
安全 C++ 容器
C++ 11新特性之语法甜点3
C++ 11新特性之语法甜点3
35 0
|
3月前
|
编译器 C++ 容器
C++语言的基本语法
想掌握一门编程语言,第一步就是需要熟悉基本的环境,然后就是最重要的语法知识。 C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。 类 - 类可以定义为描述对象行为/状态的模板/蓝图。 方法 - 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。 即时变量 - 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。 完整关键字
|
4月前
|
Java 编译器 程序员
C++中的语法知识虚继承和虚基类
**C++中的多继承可能导致命名冲突和数据冗余,尤其在菱形继承中。为解决这一问题,C++引入了虚继承(virtual inheritance),确保派生类只保留虚基类的一份实例,消除二义性。虚继承通过`virtual`关键字指定,允许明确访问特定路径上的成员,如`B::m_a`或`C::m_a`。这样,即使基类在继承链中多次出现,也只有一份成员副本,简化了内存布局并避免冲突。虚继承应在需要时提前在继承声明中指定,影响到从虚基类派生的所有后代类。**
|
4月前
|
编译器 C++ 开发者
C++一分钟之-属性(attributes)与属性语法
【7月更文挑战第3天】C++的属性(attributes)自C++11起允许附加编译器指令,如`[[nodiscard]]`和`[[maybe_unused]]`,影响优化和警告。注意属性放置、兼容性和适度使用,以确保代码清晰和可移植。示例展示了如何使用属性来提示编译器处理返回值和未使用变量,以及利用编译器扩展进行自动清理。属性是提升代码质量的工具,但应谨慎使用。
140 13
|
5月前
|
编译器 程序员 C++
C++一分钟之-属性(attributed)与属性语法
【6月更文挑战第28天】C++的属性为代码添加元数据,帮助编译器理解意图。C++11引入属性语法`[[attribute]]`,但支持取决于编译器。常见属性如`nodiscard`提示检查返回值,`maybe_unused`防止未使用警告。问题包括兼容性、过度依赖和误用。使用属性时需谨慎,确保团队共识,适时更新以适应C++新特性。通过示例展示了`nodiscard`和`likely/unlikely`的用法,强调正确使用属性能提升代码质量和性能。
89 13
|
5月前
|
编译器 C语言 C++