小尾羊的cpp学习之路,01c到cpp

本文介绍了C++的历史背景、应用领域,并详细讲述了C++相对于C语言的加强与扩展,包括命名空间、变量检测、类型检测、结构体、const和bool类型。此外,还探讨了C++在C语言基础上的引用、函数重载、函数默认参数和内联函数等特性。最后,讨论了C/C++混合编程中可能出现的问题及其解决方案。
摘要由CSDN通过智能技术生成

目录

1、C++简介

1)作者

2)历史背景

3)应用领域

4)C++内容

2.C++的第一个程序

2_1 命名空间

3.C++在C语言上的内容加强

_1.变量检测加强

_2.类型检测加强

_3.结构体加强

_4.const加强

_5.bool类型

4、C++在C语言上的扩展

_1.引用

_2.函数重载

_3.函数的默认参数

_4.内联函数

5.C/C++混合编程


1、C++简介

1)作者

1982 年,美国 AT&T 公司贝尔实验室的 Bjarne Stroustrup(本贾尼·斯特劳斯特卢普) 博士在 c 语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与 c 语言的渊源关系,它被命名为 C++。而 Bjarne Stroustrup博士被尊称 为 C++语言之父。

2)历史背景

C语言作为结构化和模块化的语言,在处理较小规模的程序时,比较得心应手。但是当问题比较复杂,程序的规模较大的时,需要高度的抽象和建模时,C语言显得力不从心。

为了解决软件危机,20 世纪80年代,计算机界提出OOP(object oriented programming)思想,这需要设计出支持面向对象的程序设计语言。Smalltalk 就是当时问世的一种面向对象的语言。而在实践中,人们发现C语言深入人心,使用非常广泛,以至于当时最好的方法,不是发明一种新的语言去取代它,而是在原有的基础上发展它。 在这种情况下 C++应运而生。最初这门语言并不叫 C++,而是C with class (带类的 c)。

自1982年被发明出之后,C++经过了多次完善,形成了现在的C++,当然C++还在扩展,每隔3年就会添加一些新的内容,目前面试/笔试提及最多的2011年发布的那一次标准。

3)应用领域

_1.系统层软件开发(操作系统,编译器等)

_2.服务器程序开发(网络上的服务器接收各种客户端请求)

_3.网络,游戏

_4.科学计算(使用计算机解决现代科学与数学中的数据计算)

如果你想一直在程序员这条路上走 =》 软件架构设计师

4)C++内容

C++语言的名字,如果看作C的基本语法,是由操作数C和运算符后++构成。C++ 是本身这门语言先是 C,是完全兼容 C.然后在此基础上++。这个++包含三大部分:

        _1.C++对 C 的基础语法的扩展。

        _2.面向对象(继承、封装、多态)。

         _3.STL 以及 不定时发布的一些扩展内容。

我们C++的学习顺序也是按照这个内容来的,先了解C语言的扩展,再学习C++的核心,最后再学习C++的扩展.

在正式的内容之前,先装个c++的编译器:

平台不限制,你可以继续使用ubuntu,需要安装编译器 g++

sudo apt-get install g++ (如果在终端上敲命令 g++ -v 没反应,就敲下这个)

如果想直接在windows上用,可以装VS系列Visual Studio(2013/2015/2017/2019)

vs code 应该也是ok的,可以装个扩展包(平台不同,编译器的处理会有点不一样,所以有时某些代码的结果会不同)

交叉编译器 :arm-linux-g++ (能敲arm-linux-gcc 就能敲这个)

2.C++的第一个程序

C++代码文件的后缀:

源文件: .cpp  .C (两种主流后缀)
头文件: .h      .hpp      无后缀(主要体现标准库中的头文件,这个东西的产生主要和C产生区别)

程序示例代码:

#include<iostream> => 等效于stdio.h 但是这个是C++中输入输出的头文件

int main()
{
    std::cout << "hello world" << std::endl;
    return 0;
}

:: 作用运算符,std是作用域的名字,cout 是作用域中的内容
{
    int a;//a的作用域就是上下两个{}之内
    {
        int b;//b的作用域就是上下两个{}之内
    }//c++允许作用域拥有名字 => 命名空间
}
cout 是标准输出流 => stdout
<< c语言表示左移,而在C++中允许我们重新定义运算符的使用规则
在cout << "hello world" 这段话中,则是表示把hello world 输入到标准输出流
endl => end line => c语言 \n  "\n" == endl

对于输出,你先记住用法:
cout << 输出值/常量/表达式 << 输出值/常量/表达式 << 输出值/常量/表达式 << 输出值/常量/表达式;
不同的输出值/常量/表达式以<<隔开

代码的运行方式:
    1.编译
    g++ cpp文件列表 -o 可执行文件    (-E -S -c g++都是支持的)
    编译流程: 预处理,编译,汇编,链接
    2.运行代码
    ./可执行文件名 (后面也可以加 -I(头文件路径) -L(库文件路径) -l(库名))
5分钟,自己过一下上面的步骤

2_1 命名空间

c++允许作用域拥有名字 => 命名空间。

写法:
    命名空间的定义格式:
    namespace 标志符名字
   {
     表示命名空间的内容,这个内容几乎可以是任何内容,变量,函数,类型定义,甚至是子命名空间。
  }
    例子:
    namespace test
    {
        int a = 10; //我建议是写声明,多文件使用会出问题。。。
        int mysum(int a,int b)
        {
            std::cout << a+b << std::endl;
        }
        namespace child
        {
            int b = 12;
            int test_func()
            {
                std::cout << "test" << std::endl;
            }
        }
    }
用法:
    3种:
    直接通过 命名空间名::成员 去操作
    test::a = 12; //直接修改命名空间中a的值
    先添加作用域使用的声明语句,后续的使用不需要再加 命名空间名::
    声明格式:
        using    命名空间名::成员;    单个成员使用声明
        using namespace 命名空间名;  该命名空间声明,之后成员的使用都不需要命名空间名:: 前缀
    using test::a; => a = 12;
    using namespace test; a = 12;
    
对于命名空间,我们用来避免命名冲突的,大型工程动不动就是上千个.c,函数名/变量名 光是命令就是头皮发麻,为了优化这种问题,我们常常把一个.c中的函数/类型定义/全局/静态变量等内容放到一个命名空间中,这样就能使得对象取名的的难度下降10倍以上。
示例代码:
test.cpp test.h

输入操作:
#include<iostream>
using std::cin;
使用方式:
cin >> 变量 >> 变量 >> 变量;
示例代码见hello.cpp

//test.h文件
//pragma once =>C++头文件只需要这一句,就等效于下面3句话
#ifndef __TEST_H
#define __TEST_H

namespace test
{
    int a;
    int func();
    int sum();
    void prin();
} 

#endif
//test.cpp文件
#include"test.h"
#include <iostream>

using std::cout;
using std::endl; //因为不知道std中到底有多少东西,一般只写要用的,免得重名操作

int test::a;

int test::func()
{
    cout << "func" <<endl;
    return 0;
}

int test::sum()
{
    return 0;
}

void test::prin()
{
    cout << "prin" <<endl;
    return 0;
}
//hello.cpp文件
#include<iostream>//=> 等效于stdio.h 但是这个C++中输入输出的头文件

using namespace std;
//std是标准输入输出库对应的命名空间
/*
using std::cout;
using std::endl;//像这种用法,一般都是担心多个命令空间中有同名的
*/  
namespace test
{
    int a = 10;
    int sum(int a,int b); //写个声明也是可以的
    namespace child
    {
        int m = 100;
    }  
}
//如果函数的声明在一个命名空间中,那么定义时要说明命名空间的名字
int test::sum(int a,int b)
{
    return a+b;
}
//如果只是定义命名空间,但是不声明 => 等同于没用
using test::child::m;
using test::sum;
int main()
{
	cout << sum(m,12) << endl;
    int value;
    cin >> value;
    cout << value << endl;
	return 0;
}

3.C++在C语言上的内容加强

_1.变量检测加强

原先C中:
全局变量:
int a;// 当做是声明
int a = 100; //C语言没问题
C++这样写,会报错,因为int a,编译器将其看作是定义,如果你写了extern 那么任然是声明

so,声明的时候最好+ extern。

_2.类型检测加强

函数参数与返回值问题:
    对于C语言,函数可以不写返回值,那么会默认是int类型返回
    函数可以空着参数,那么其实这个函数的实参列表可以填很多个
sum(int a,int b)
{
    return a+b;
}//虽然编译器会警告,但是还是会给你附带上int返回值类型
void func()
{
}
func(1,2,3,4,5,6); //还是可以调用func函数

而C++不允许一个函数没有返回值类型,且参数为空则默认是void


枚举检测:
    enum xxx{M,N,Q,K};
    枚举变量定义时,只能以内部成员名字初始化

_3.结构体加强

在原先的C结构体中,一般只能写变量/子结构体,而C++中,结构体中可以写函数,且这个函数是属于这个结构体的。

struct xxx  => typedef struct xxx  xxx;
{
    int a;
    int double_num(int a)
    {
        return a*2;
    }
};
除了可以定义函数之外,结构体类型构建时,可以不写struct
struct xxx v; 
xxx v;

_4.const加强

const 修饰词汇,被该词汇修饰的变量是一个只读的变量(只能放在=的右边),亦或者是叫一个常量。
C应用:
    const int a = 10; //一个只读变量在定义时,必须赋予初始值
    int b = 100;
    int *const c = &b;// 这种情况,指针常量(指向的是一个常量),在这种情况下,你能进行的操作是*c
    //但是你不能 c = &x; 不能重新赋值/指向其他的对象
    const int *d = &b;//这种情况,常量指针,这种情况下,你不能进行的操作是*c = xxx;
    //但是你能 c = &x;
    如果const离变量近,表示变量的值不可改,指向的对象不可改!!
    如果* 离变量近,表示指向对象的内容不可以改变!!!

    
    在C语言中,一般来说,一个变量就算是常量,但是它依然会拥有一个地址,我们可以通过指针去修改这个只读变量的值(危险的,不要操作)
    在C++中,常量是真的常量,一个变量在如果是一个只读变量,那么在编译期间,这个变量 =》 常量,这个变量的值将会保存在常量区,如果你试图通过指针修改这个常量的值,那么编译器会给出一个临时地址去给这个指针变量,以防止数据的修改。
    const int a = 100;
    int * b = (int *)&a;//指向常量,不可能的,给个替身给你
    *b = 10;
    cout << a << endl; 输出100

_5.bool类型

在C语言中,没有特定的类型表示逻辑真与假,只能通过类型的定义/宏去解决这个问题,C++则对此进行加强,衍生bool类型

bool类型是专门用于表示逻辑上的真与假的,所以在设定上只给与这个类型两个值以表示真假。
    0 -> 假 false   1 -> 真 true   且两个英文单词就是这两个值对应的宏名
    如果你试图赋予bool类型变量非0/1的值项,那么编译器会给你强转成0/1 => 实际上这个类型一般只用一个bit表示数据,但是这个类型一般占一个字节,如果你连续的定义了多个bool类型变量,那么可能有的编译器会把多个bool类型变量的数据保存在一个字节中。
定义格式:
    bool 变量名=值;
示例代码:
    bool a = 100;
    bool b = 0;

4、C++在C语言上的扩展

_1.引用

在C语言中,往往存在着这种问题,如果我要一个局部变量的作用域(函数内变量)外访问它,那么都需要通过指针,使用指针必然涉及到*与&符号的频繁使用,对于指针理解不深刻的初学者可能就倒在了指针中,为了避免这种情况,C++提出了使用引用去替代一部分指针使用情况,从而使得变量的操作不再那么复杂(这主要体现在函数参数与返回值的使用上),当然如果你需要使用heap,那么指针是避免不了的

引用是什么?
变量的名字:变量的名字是变量所表示的那段存储空间的标志符,我们访问那段空间,一般都是通过变量名字(当然也可以通过指针),但是使用变量名更方便,而引用这个操作,是一个可以使一个变量拥有多个名字的一种操作。

 引用的写法与用法:
    定义格式:
    数据类型 &变量名 = 已存在的变量名; //引用在定义时必须赋初值
以上面这种方式定义一个新变量之后,那么两个变量会指向同一个地址
    例子:
    int a = 100;
    int &b = a;
    int &c = b;
    int &d = a;
    a == b a与b表示同一个东西,操作a就是操作b
用途:
    1.作函数参数,替代一级指针
    2.作返回值,可作为左值使用
简单的看,就是给一个变量起别名:

_2.函数重载

重载 => 重复载入
函数重载 => 一个代码中,可以有多个函数名一样的函数
函数重载主要起到了,去除函数重名时编译器的报错问题,但是实际上是因为有人“帮忙解决了问题”。。。

函数重载的写法:
    C++允许函数名相同,但是"参数列表不同"的函数同时存在,返回值根本不需要考虑

参数列表不同 => 个数不同,参数类型不同(面试问点)
    
int sum(int a,int b)
{
    return a+b;
}

double sum(double a,double b)
{
    return a+b;
}

int sum(int a,int b,int c)
{
    return a+b+c;
}

函数的重载的实现方式:
    C++编译器的函数换名机制 => 两种方式可验证
    方式1: 看汇编代码
        _Z3sumii:
        _Z3sumdd:
        _Z3sumiii:
    方式2: 通过nm指令,看符号表。 nm xxx.o
    0000000000000014 T _Z3sumdd
    0000000000000000 T _Z3sumii
    000000000000002e T _Z3sumiii

作业:
    仿函数重载方式,写两个函数
    第一个函数 => 求整型一维数组的最小值
    第二个函数 => 求字符一维数组的最小值
见homework.c

//homework.cpp文件

#include <iostream>
using namespace std;

/*
作业:
	仿函数重载方式,写两个函数
	第一个函数 => 求整型一维数组的最小值
	第二个函数 => 求字符一维数组的最小值
*/

int intArrayMin(int *a, int n)
{
	int min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	cout << min << endl;
	return min;
}

unsigned char intArrayMin(unsigned char *a, int n)
{
	unsigned char min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	cout << min << endl;
	return min;
}


void func()
{
	int n;
	cout << "请输入数组长度" << endl;
	cin >> n;
	unsigned char*a=new unsigned char[n];//申请数组空间,int*p=new int[10];//10个元素空间
	cout << "请输入数组元素" << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	unsigned char min = intArrayMin(a, n);
	cout << "最小值为:" << min << "  ASCII值为:" << (int)min << endl;
}



int main()
{
	func();
	return 0;
}

_3.函数的默认参数

C++可以为函数的参数提供默认值,主要是为了解决一些函数的某个参数总是一个值的情况,但是偶尔会有所不同。
写法:
返回值类型 函数名(参数列表)
    参数列表=> 类型1 名字1 = 默认值1,    类型1 名字2 = 默认值2
    如果你不想给某个参数设置默认值,那么你应该把这个参数放在参数列表的前面,

默认参数的原则是靠右原则:一旦当前参数设置了默认值,那么它右边的所有参数都需要设置默认值 。

为什么呢?,因为函数调用不存在 func(1,  ,2) 这种情况
    
例子:
    int sum(int a,int b,int c = 10,int d = 10)
    {
        return a+b+c+d;
    }
    int sum(int a,int b = 10,int c) //error


示例代码:
    overload.c

说明:函数调用时,没有对应的实参,则采用默认值,有实参就用实参值
ps:
    int sum(int a,int b)
    int sum(int a=10,int b = 10,int c = 10)
    ambiguous => 模棱两可
    
    默认值一般写声明中,不写函数定义中

//overload.cpp文件

#include<iostream>
using namespace std;
inline int sum(int a = 10, int b = 20);//用在声明中就行

int sum(int a, int b)
{
	return a + b;
}

double sum(double a, double b)
{
	return a + b;
}

int sum(int a, int b, int c)
{
	return a + b + c;
}


int main()
{
	cout << sum(1, 2, 3) << endl;
	cout << sum(1) << endl;
	cout << sum(1.2, 2.2) << endl;
}

_4.内联函数

int sum(int a,int b)
{
    return a+b;
}

int main()
{
    int a,b;
    for(int i = 0;i < 10;i++)
    {
        cin >> a >> b;
        sum(a,b);
    }//这是一段有病的代码,为什么呢,你为何要把一句话写成一个函数??
}//函数的调用 会有 栈创建、入栈、出栈、销毁栈等操作
//这4个操作花费的时间,肯定比a+b花的时间长,有必要考虑这么多吗?要的,这些小东西决定了你最后组装出的是拖拉机还是小奔(当然你放心,你现在去找工作不会问你这种东西,如果问了,他的意思是看你有没有代码优化的思想)

所以在这种情况下,我们是可以把sum替换成a+b的,but有的时候,我不想这么干,有时写个函数能表达的意思肯定比一句话更完善,比如:
    屏幕操作是不是有个画点函数:
    draw_point(int x,int y,int color)
    *(plcd + 800*y + x ) = color;
这种时候就会有一个矛盾的观念,我既想代码实际上是*(plcd + 800*y + x ) = color;,但是我也希望看着是draw_point(int x,int y,int color)

解决这种问题,就需要使用到内联函数:
内联函数格式:
inline 返回值类型 函数名(参数列表){};

inline这个关键字加在函数前面,这个函数就是一个内联函数
但是有个问题,我不能给你证明这个替换操作
    这个替换操作是在编译时替换的 => 看汇编
    虽然编译器知道你这个函数需要内联 => 但是它做不做要看编译器自己怎么处理 (至少简单的代码在g++上看不出结果)
    
    并且需要注意的是,代码行数超过10行,就不需要内联了,没必要的

这个东西看作是思想上的体现就行,因为实际操作要看编译器选择

5.C/C++混合编程

C++是从C语言上扩展出的,是向下兼容C语言的,所以在Cpp文件中,也可以完全写C形式代码,但是有时候,某些库函数接口是通过gcc编译器编译出来的,如果我们在cpp文件中使用这些函数,那么如果最终的代码使用g++编译 =》 出问题

test.c

int sum(int a,int b)
{
    return a+b;
}

test.h
int sum(int a,int b);

gcc  -share  -fpic  -o  libtest.so   test.c    =>把这个c文件编译成了动态库

main.cpp
#include"test.h"
int main()
{
    sum(1,2);
}

china@ubuntu$ g++ main.cpp -ltest -L./
/tmp/cccchAaw.o:在函数‘main’中:
main.cpp:(.text+0xf):对‘sum(int, int)’未定义的引用
collect2: error: ld returned 1 exit status

为什么会找不到呢? 重载 => 函数换名
g++ 编译器会给函数换名,但是gcc编译器不会,所有两个编译器编出来的函数格式不对,就找不到。
解决这种问题的方式 => 修改头文件中的声明
当你使用的函数是c编译器编出来的,那么你需要使用特殊的声明语句包含内容


extern "C" //这个声明语句表示,接下来{}中的内容将会使用C的方式去找
{

}

#ifdef  __cplusplus //使用C++编译,该宏定义,所以extern "C"有效,但是C编译器,不带这个宏,extern "C"无效
extern "C" //这个声明格式是C++专属的,C语言编译器不认识
{
#endif
    int sum(int a,int b);
#ifdef  __cplusplus
}
#endif


=》详细情况 =》test2.h

自己敲一下,虽然这个东西目前用的少,还是留点印象。

//test2.h文件

#ifndef __TEST_2
#define __TEST_2

//在C++编译器中有一个预定义的宏 __cplusplus ,当你使用了c++编译器时,肯定会有这个宏的定义


#ifdef __cplusplus //使用C++编译,该宏定义,所以extern "C"有效,但是C编译器,不带这个宏,extern "C"无效
extern "C" //这个声明格式是C++专属的,C语言编译器不认识
{
#endif
    int sum(int a,int b);
#ifdef __cplusplus
}
#endif

#endif
//main.cpp文件

#include"test2.h"
#include<iostream>
using namespace std;

int main()
{
    cout << sum(1,2) << endl;
}

Thank you, Mr. T.Z. for teaching me this lesson, on September 7, 2021, at the beginning of my senior year.

End2022,3,9

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牙牙将心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值