C++基础入门

CSDN成就一亿技术人

此文建议有C语言基础的观看

目录

一.输入输出(I/O)

cout输出流:

cin输入流:

二.命名空间

三.函数重载

一.函数默认参数(C语言不支持):

二.函数重载概述:

[三.函数重载的原理--名字修饰(name Mangling):](#三.函数重载的原理--名字修饰(name Mangling):)

四.引用

五.内联函数

六.auto关键字

七.基于范围的for循环


0基础小白学C语言看这一篇就够了(C语言详讲)-CSDN博客https://blog.csdn.net/lh11223326/article/details/136971590?spm=1001.2014.3001.5501

我们知道C++是由C语言发展而来的,几乎完全兼容C语言,换句话说,你可以在C++里面编译C语言代码。如下图:

C语言是面向过程的语言,C++在C语言之上增加了面向对象以及泛型编程机制,因此C++更适合中大型程序的开发,然而C++如果不使用高级特性,它的效率跟C并无差异。

C++是"C Plus Plus"的简称所以很多C++程序后缀都是.cpp,从语法上C语言是C++的一部分,所以C语言可以直接写在C++里面,现在对C和C++来说是两门独立的语言,但是在早期并没有C++,但是有"带类的C",带类的C是C语言的一个扩展和补充,它增加了很多新语法,目的是提高开发效率,这时的C++仅支持简单的面向对象,更没有自己的编译器,只是通过一个预处理程序(cfront),把C++代码翻译成C语言代码,再通过C语言编译器合成最终程序。随着C++语法的完善,它能够支持面向过程编程,面向对象编程和泛型编程,成了一门独立的语言,拥有了自己的编译方式。

C++关键字

在C中关键字有32个,而C++有63个关键字,其中有32是C语言的,如下:

asm do if return try continue auto double inline short typedef for bool dynamic_cast int signed typeid public break else long sizeof typename throw case enum mutable static union wchar_t catch explicit namespace static_cast unsigned default char export new struct using friend class extern operator switch virtual register const flase private template void true const_cast float protected this volatile while delete goto reinterpret_cast

一.输入输出(I/O)

首先我们需要引入一个C++中头文件#include<iostream>用来支持输出输出。

cout输出流:

如下代码中,因为cout在std命名空间里所以要加std::,代码中使用cout来输出一句"hello world!"而他们之间的<<符号像一种水流的形式把"hello world!"流入cout,然后cout把流入的数据输出到终端(控制面板)上。这就是一个基本的输出.

cpp 复制代码
#include<iostream>
int main(){
    std::cout<<"hello world!";
    return 0;
}

但是cout还能自动识别对应的C++类型,如下:

上述代码使用了endl来换行,同样也要在前面加std::不然会报错,或者也可以在字符串里面加\n同样也可完成换行功能。同样cout可以跟printf一次输出多种数据,如下:

可以看到C++的输出流不用输入对应的格式控制符他能自己检查对应的类型。每次使用endl的时候都需要加std::很不方便,但直接展开使用会有冲突的风险。指定展开可以解决这个问题;

如,我要展开endl的命名空间格式如下:

using std::endl;

这样就可以指定展开endl了而又不用担心全部展开的风险,同样cout也可以用同样的方式展开

cin输入流:

cin输入跟cout的输出相反,cout是从一个地方拿出数据输出,cin是从控制台或者其他地方拿取数据放到另一个地方去,cin的示例代码如下:

上述代码中cin的输入操作,运用的是和输出相反的>>符号,就像是把数据流到对应的变量中一样,然后放入对应的变量按照对应变量的顺序依次放入数据。

二.命名空间

在C/C++中,变量,函数和和类这些名称都存在于全局作用域中,可能会导致很多冲突,使用命名空间的目的是对标识符的名称进行本地化,避免命名冲突或名字污染,namespace关键字就是解决这种问题的。如下程序并无问题:

cpp 复制代码
#include<stdio.h>
int rand=0;
int main(){
    printf("%d\n",rand);
    return 0;
}

但是如果在上述代码中加入一段#include<stdlib.h>,因为stdlib.h头文件里面有一个rand的函数,此时就会出现如下错误:

命名冲突(C语言中没有方法可以解决这个问题):

1.我们写的代码跟库冲突

2.我们互相之间写的代码冲突

这时C++中就有了namespace用来定义一个命名空间,语法:namespace 命名空间名{成员},示例如下:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
namespace ThisLocality{
	int rand = 0;
}

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

这时就解决命名冲突这个问题,命名空间就像一堵墙把rand围起来了,此时的rand默认访问的是全局就是stdlib.h头文件里面的函数rand,而namespace里的rand就不会被访问了,既然是访问函数rand那么就用%p来打印地址,此时代码运行如下:

此时想访问namespace里面rand,只需要在rand前面加,命名空间名::(域作用限定符)变量名就可以了,示例代码如下:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
namespace ThisLocality{
	int rand = 0;
}
int main() {
	printf("%p\n", rand);//访问的是stdlib.h头文件里面的函数
	printf("%d\n", ThisLocality::rand);//此时访问的是bit命名空间里的rand变量
}

此外,命名空间除了可以定义变量也可以定义函数,结构体,还可以嵌套,代码如下:

cpp 复制代码
namespace ThisLocality{
    //定义变量
    int rand=10;
    //定义函数
    int Add(int left,int right){
        return left+right;
    }
    //定义结构体
    struct Node{
        struct Node*next;
        int val;
    };
    
    //嵌套
    namespace ThisLocality2{
        int rand=100;
    }

}

上述几种定义的访问方式如下图:

上述中函数和变量的访问方式只不过就是在名字后面加了()里面放对应的参数,而定义命名空间中的结构体的时候命名空间名是加在结构体名前面的所以是ThisLocality::Node 结构体变量名,最后访问int rand=10;首先要找到命名空间ThisLocality然后再::命名空间名,这是找命名空间里的命名空间最后就到了这个命名空间里了,最后::变量名就可以访问了,我们知道嵌套如果太深也不好一般来说两层基本就够了。

下面在Stack.h中定义了ThisLocality然后在Stack.cpp中完成的ThisLocality里面函数的内容,因为他们是同名所以会自动合并成一个命名空间,所以在Test.cpp中导入Stack.h就可以使用ThisLocality中的函数了。

展开命名空间,使用展开命名空间就像把命名空间里面的代码直接放到展开位置上了,使用了展开命名空间之后就不需要再使用,命名空间::这段了可以直接使用里面的变量或者函数跟普通变量使用方法并无区别。

using namespace 命名空间名;

展开示例:

可以看到展开之后不需要加,命名空间名::,如其中的ST它会先去全局中找没找到就会去声明的命名空间里面找,如果在这两个地方都没有找到就会报错,但是不建议大量使用这种方法。

我们经常会在很多C++的代码中看到:using namespace std;这段代码,这段代码其实是C++官方库里面的命名空间,这里是直接把这个库展开了,这样就可以随便用里面的东西了。C++把东西放在里面,就是因为容易发生冲突,如果展开那不是本末倒置了,但是日常小程序为了方便可以这样做。

三.函数重载

自然语言中,一个词可以有多重含义,可以通过上下文来判断该词真是的含义,即该词被重载了。

一.函数默认参数(C语言不支持):

默认参数的概念,是声明或定义函数时为函数的参数赋值,在调用该函数时,如果没有指定实参则采用默认形参来替代,否则使用指定的实参,默认参数的值必须是全局变量或者常量。如下:

还可以把函数的全部参数变为默认参数,如下:

可以看到如果只传一个参数那么只会把第一个函数的第一个代替,因为他们是一一代替的,所以如果是c没有默认参数那么要传三个参数,第三个才能接收到。

而且默认参数必须是从右往左连续的的,如果不是那会报错,如下所示:

如:void Func1(int a=10,int b=20,int c=30); 有四种调用方式,如下:

Func1();

Func1(0);

Func1(2,1);

Func1(3,1,2);

因为函数是一个一个的接收按照顺序来放置传进来的参数的,所以不能这种:Func1(,2);这样是传不到第二个参数哪里的,因为你传几个参数它才会接收到第几个参数会报错,这样只传了写了第二个的值的第一个为空所以无法接收。

如果函数有默认参数,声明和定分离的时候只能在声明的地方给默认参数。

二.函数重载概述:

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

cpp 复制代码
#include<iostream>
using namespace std;

int Add(int left, int right) {
	cout << "int Add(int left,int right)" << endl;
	return left + right;
}
double Add(double left, double right) {
	cout << "double Add(double left,double right)" << endl;
	return left + right;
}
int main() {
	cout <<"整数的和:" << Add(1, 2) << endl;
	cout << "小数的和:" << Add(2.3, 23.1) << endl;
	return 0;
}

上述实现整数,小数相加的操作,Add(1,2);传的两个参数都是整数,刚好跟int Add(int left,int right);的参数相匹配所以就是调用一个Add,同理 Add(2.3, 23.1);参数与第二个Add相匹配也是直接调用第二个Add,代码运行结果如下图:

可以看到当函数名相同时调用哪个函数取决于传递的参数。

如果我传Add(1,2.3);一个整形一个小数,那么它会直接报错,因为我们对Add使用了函数重载那么它会就去找那一个传进去的参数和函数参数一样的,如果找不到则证明没有,但是我们知道整数和小数之间是可以隐式转换的,但是上述代码它该转成那一个呢编译不知道会直接报错。如果代码中只有两个函数的其中一个它会自动转换的不会报错。

函数重载时里面也可以有默认参数的,并不会影响函数重载,因为函数重载只看参数的类型不看默认参数的。

函数返回值不能构成重载的原因是,如果返回值可以重载那么两个参数一样但是放回值不同的函数那应该调用那一个呢,很显然编译器并不知道,所以返回值不能构成重载。

三.函数重载的原理--名字修饰(name Mangling):

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

我们知道在预处理阶段会把头文件展开(意味着把头文件的内容拷贝到展开的位置)/宏替换/条件编译/去掉注释...这就生成了一个文件名.i后缀的文件,在编译阶段会检查代码的语法并生成汇编代码然后生成了文件名.s后缀的文件,汇编阶段把代码转换成二进制的机器码然后生成了文件名.o后缀的文件,链接的阶段是把所有文件的代码合并在一起,链接一些没有确定函数地址等(函数之后在调用时才会创建,然后才会有对应的地址)生成文件名.out文件。

C语言没有重载的原因是:C语言在链接函数地址时,用函数去找。(C不存在同名函数,因为它找函数使用函数名去找的),而C++是把参数代入进去修饰了,然后使用修饰之后的函数名去找的。从下图的C++程序中可以看到,如函数名相同但是参数不同链接时生成的函数名也不同,所以C++可以重载因,为他们链接时函数名就不同了,Windows下修饰的函数名:

四.引用

引用不是新定义一个变量,而是给已经存在的变量取一个别名,编译器不会为了引用变量开辟内存空间,它和它引用的变量公用同一块内存空间。如李白被称为诗仙。李白和诗仙都是同一个人。

语法:

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

特性:

引用在定义时必须初始化

一个变量可以有多个引用

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

示例:

cpp 复制代码
void TestRef(){
    int a=10;
    int &ra=a;//定义引用类型引用a
    printf("%p\n",&a);
    printf("%p\n",&ra);
}

需要注意的是,引用类型必须和引用实体是同种类型的。,他们之间的关系图如下:

可以看到他们都可以使用同一块内存空间,而且使用方法都是相同的,而且a可以取多个别名,相比指针少了一些更麻烦的操作。

引用可以用来做函数参数代码如下:

cpp 复制代码
void Swap(int &left,int &right)
{
    int temp=left;
    left=right;
    right=temp;
}
int main(){
    int a=3,b=7;
    Swap(a,b);
    return 0;
}

这样不需要传地址也能交换两个实参的值,而且使用起来可以减少很多麻烦。引用和指针的区别,在语法概念引用就是一个别名,没有独立的空间,和其引用的实体公用同一块空间。因为引用不能更改而指针可以改所以引用不能进行比如逐个访问数据这种操作。所以引用很大程度上面代替不了指针。因为引用和核心是用来做参数,返回值的。函数的形参改变实参这时形参就像实参的别名,所以可以使用引用来减少代码量完成同样的工作,而且数值量比较大的时候使用引用能提高效率。

五.内联函数

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

如有一个相加两数的函数:

cpp 复制代码
int ADD(int a,int b){
    return a+b;
}

如果在上述函数前面添加inline关键字将其改成内联函数,在编译期间会替换调用函数体的函数的调用位置。这类似于C语言中的宏的替换:

cpp 复制代码
#include<iostream>
using namespace std;
#define ADD(x,y) ((x)+(y))
#define ADD1(x,y) ((x)+(y));
int main() {
    cout << ADD(23, 87);
    //上述代码中的ADD替换成 宏中的内容 
    //cout<<((23)+(87));


    //宏后面不能加;是因为宏是完全替换的操作如下就会出错
    cout << ADD1(23, 24) << endl;
    //因为其替换之后代码为:
    //cout<<((23)+(24));<<endl;  所以一般不建议在宏后加;
}

宏优点:直接替换调用位置的代码,不用建立函数栈帧,提高效率

宏的缺点:

容易出错,语法细节要求多

而且宏不能调试(在预处理阶段就替换了)

还没有类型的检查(如我传一个int和char会导致结果不理想)

C++中就用:enum const inline来替代宏

enum和const是宏常量

inline是宏函数

而inline却跟正常函数一样,可以调式,有类型检查

使用inline函数的代码如下:

cpp 复制代码
inline int ADD(int a,int b)
{
    int c=x+y;
    return c;
}
int main(){
    int ret1=ADD(23,2);
    //不用建立函数栈帧
    return 0;
}

从上可以看到其语法跟普通函数没有不同只是在函数前面加上了inline。

在release模式下,可以查看编译器生成的汇编代码中是否存在call Add ,而在debug模式下,需要对编译器进行设置,否则不会展开(替换)。

特性:

由上可知inline是一种以空间换时间的做法,如函数被编译器当做内联函数则会在编译阶段,会用函数体替换函数调用,这可能会导致文件变大,但是少了调用开销可以提高程序运行效率。

inline对于编译器来说只是建议,在不同的编译器中inline的实现也有所不同。

inline修饰:函数不是很大(取决于对应的编译器),不是递归,也不频繁调用的函数可以使用inline修饰,不然编译器会忽略inline的特性。

使用inline时并不建议将声明和定义分开,因为这样会导致链接错误,因为inline被展开,然后就没有了函数地址,链接就会找不到。

六.auto关键字

auto可以自动推导类型,比如把整形的a变量赋值给它定义的变量他就是整形,我们可以通过typeid来打印数据的类型,如下代码:

cpp 复制代码
int main() {
	int a = 0;
	auto c = a;
	auto d = &a;
	auto* e = &a;
	auto& f = a;
	f++;

	//typeid打印类型
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;
	cout << typeid(f).name() << endl;
	return 0;
}

运行结果为:

  • int
  • int *
  • int *
  • int

由上可以看到auto定义的c是赋值变量a的类型,而d变量则为指针所以它才能接收&a,也可以自己写*把其定义为指针类型,还可以&把定义变量改为引用类型。


随着程序越来越复杂,程序中用到的类型也越来越复杂,导致类型难于拼写,含义不明导致容易出错,如:

std::map<std::string,std::string>::iterator是一个类型,但是类型太长,容易写错。

所以auto的价值主要还是用来替代较长类型的定义。如:

vector<string>::iterator it=v.begin();

auto it=v.begin();

用auto声明指针类型时,用auto和auto*并无区别,但用auto声明引用类型时必须加&

auto在实际中最常见的优势就是跟C++11提供的for循环,还有lambda表达式进行配合使用。


使用auto时需要注意的是:

  • 在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
  • auto不能作为函数参数。
  • auto不能用来声明数组。
  • 在C++11中只保留了auto作为类型指示符的用法

七.基于范围的for循环

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错。所以在C++11中引入for基于范围的for循环。


for循环后的括号由冒号":"分为两部分:第一部分是范围内用于迭代的变量,第二部分是表示被迭代的范围。代码如下:

cpp 复制代码
 void TestFor()
 {
     int array[]={1,2,3,4,5};
     for(auto&e:array)
         e*=2;//将array里每个元素乘2
     for(auto e:array)
         cout<<e<<".";
 }

与正常的for循环并无区别,可以使用continue来结束循环,也可以用break来跳出循环。

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


对于下面没有明确 数组的范围,这种代码就有错:

void TestFor(int array[]){

for(auto&e:array)

cout<<e<<endl;

}

谢谢观看

下一篇建议:

C++类和对象基础-CSDN博客https://blog.csdn.net/lh11223326/article/details/136834917?spm=1001.2014.3001.5501

相关推荐
林开落L2 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色3 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧11 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices15 分钟前
C++如何调用Python脚本
开发语言·c++·python
单音GG18 分钟前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
我狠狠地刷刷刷刷刷28 分钟前
中文分词模拟器
开发语言·python·算法
wyh要好好学习31 分钟前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf
AitTech32 分钟前
C#实现:电脑系统信息的全面获取与监控
开发语言·c#
qing_04060334 分钟前
C++——多态
开发语言·c++·多态
孙同学_34 分钟前
【C++】—掌握STL vector 类:“Vector简介:动态数组的高效应用”
开发语言·c++