自学内容网 自学内容网

C++ 的第一个程序

目录

一 . C++的第一个程序

 二 . 命名空间

2.1 namespace的价值

2.1 namespace 的定义

7.3 命名空间的使用

三 . C++输入&输出

四 . 缺省参数

五 . 函数重载

六 . 引用

6.1 引用的概念和定义

 6.2 引用的特性

 6.3 引用的使用

6.4 const 引用

6.5 指针和引用的关系(笔试常考)


一 . C++的第一个程序

C++兼容C语言绝大多数的语法,所以C语言实现的 Hello World! 依旧可以运行 , C++中需要把定义文件代码后缀改为 .cpp , vs 编译器看到的是 .cpp 就会调用C++编译器编译,linux下要用g++编译,不再是gcc 

#include <stdio.h>
int main()
{
printf("Hello World!");
return 0;
}

当然 , c++也有自己的一套输入输出 , 代码如下 :

#include<iostream>
using namespace std;
int main()
{
cout << "Hello World!"<<endl;
return 0;
}

 二 . 命名空间

2.1 namespace的价值

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

c语言项目类似下面程序这样,出现了命名冲突的问题 , c++ 引入namespace 就是为了更好的解决这类问题 !

 全局变量 rand 与 头文件 stdlib.h中 的函数rand 名字发生冲突 , namespace的引入是为了更好的解决这一个问题 。

2.1 namespace 的定义

  •  定义命名空间 , 需要使用到namespace 关键字 , 后面跟命名空间的名字 , 然后使用花括号 { } 即可 , { } 里即为命名空间成员。命名空间中可以定义变量/函数/类型等

  •  namespace 本质是定义出一个域 , 这个域跟全局域各自独立不同的域可以定义同名变量 ,所以 上述代码 的rand 不再冲突了 。

#include <stdio.h>
#include <stdlib.h>
namespace bit
{
int rand =10 ;
}
int main()
{
printf("%d\n", bit::rand);
return 0;
}

 这里的 :: 符号 : 命名空间限定符 , 用于指定特定的作用域或者是命名空间 ;例如 bit :: rand 表明 我们需要访问的是bit 命名空间下的rand类型 , 如果没有这个符号,可能导致命名冲突 。

  • C++中域有函数局部域 , 全局域 , 命名空间域 , 类域 ; 域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑 , 所以有了域隔离 , 名字冲突就解决了 。局部域和全局域除了会影响编译查找逻辑 , 还会影响变量的生命周期 , 命名空间域和类域不影响变量的生命周期


#include <stdio.h>
#include <stdlib.h>
namespace bit
{
int rand =10 ;
}
int main()
{
//这里指的是访问全局的rand函数的地址
printf("%p\n", rand);
//这里是访问指定命名空间bit里的rand
printf("%d\n", bit::rand);
return 0;
}

  • namespace 只能定义在全局 当然也可以嵌套定义。


#include <stdio.h>
namespace bit
{
namespace zhangsan
{
int age = 18;
int add(int x, int y)
{
return x + y;
}
}
namespace lisi
{
int age = 19;
int add(int x, int y)
{
return x + y;
}
}
}
int main()
{
printf("%d\n", bit::zhangsan::age);
printf("%d\n", bit::lisi::age);
printf("%d\n", bit::zhangsan::add(1, 2));
printf("%d\n", bit::lisi::add(3, 2));
return 0;
}

  • 项目工程中多文件中定义的同名 namespace 会认为是一个namespace , 不会冲突。

stack.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

namespace bit
{
typedef int STDataType;
typedef struct Stack
{
STDataType * a;
int top;
int capacity;
}ST;
void STInit(ST * ps, int n);
void STDestroy(ST * ps);

void STPush(ST * ps, STDataType x);
void STPop(ST * ps);
STDataType STTop(ST * ps);
int STSize(ST * ps);
bool STEmpty(ST * ps);
}

stack.c

#include"Stack.h"
namespace bit
{
void STInit(ST * ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}

// 栈顶
void STPush(ST * ps, STDataType x)
{
assert(ps);

// 满了, 扩容
if (ps->top == ps->capacity)
{
printf("扩容\n");
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity* 2;
STDataType * tmp = (STDataType*)realloc(ps->a,newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}

ps->a = tmp;
ps->capacity = newcapacity;
}

ps->a[ps->top] = x;
ps->top++;
}


}

  • c++ 标准库都放在一个叫std (standard) 的命名空间中 。

7.3 命名空间的使用

编译查找一个变量的声明/定义时 , 默认只会在局部或者全局查找 , 不会到命名空间里面去查找 , 所以下面程序会编译报错 。 所以我们要使用命名空间中定义的变量/函数,有三种方式 : 

1 . 指定命名空间访问 , 在项目中推荐这种方式。

2 . using 将命名空间某个成员展开 , 项目中经常访问的不存在冲突的成员推荐这种方式。

3 . 展开命名空间中全部成员 , 项目不推荐 , 冲突风险很大 , 日常小练习程序为了方便推荐使用 。  

总之就是 , 想使用命名空间的成员就需要告诉编译器,这个变量要到命名空间里找

-----> 1 . 可以用 :: (命名空间限定符)

-----> 2.  using 展开需要使用的命名空间

#include <stdio.h>
namespace bit
{
int a = 10;
int b = 20;
}

//编译报错:"a"未声明标识符
int main()
{
printf("%d\n", a);
return 0;
}



//指定命名空间访问
int main()
{
printf("%d\n", bit::a);
return 0;
}


//using将命名空间中某个成员展开
using bit::a;
int main()
{
printf("%d\n", a);
printf("%d\n", bit::b);
return 0;
}

//展开命名空间中全部成员
using namespace bit;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}

三 . C++输入&输出

  • <iostream> 是 Input Output Stream 的缩写 , 是标准的输入,输出流库 , 定义了标准的输入,输出对象。
  • std::cin 是istream 类的对象 , 它主要面向窄字符 ( narrow characters (of type char)) 的标准输入流 。
  • std::out 是 ostream 类的对象 , 它主要面向窄字符的标准输出流。
  • std::endl 是一个函数 , 流插入输出时 , 相当于插入一个换行字符 + 刷新缓冲区。
  • << 是流插入运算符 , >> 是流提取运算符 。(c语言还用这两个运算符做位运算左移/右移)
  • 使用c++ 输入输出更方便 , 不需要像print / scanf 输入输出时那样 , 需要手动指定格式 , c++ 的输入输出可以自动识别变量类型 ( 本质是通过函数重载实现) , 重要的是 , c++流能更好的支持自定义类型对象的输入输出。
  • IO流涉及类和对象 , 运算符重载 , 继承等很多面向对对象的知识
  • cout / cin / endl 等都属于c++ 标准库 , c++ 标准库都放在一个叫 std ( standard ) 命名空间中 , 所以需要通过命名空间的使用去使用他们                                                        --------------->        using namespace std;

日常练习中 , 我们可以使用 using namespace std , 但是在实际项目开发中 , 不建议使用。 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

int main()
{
int a = 0;
double b = 0.1;
char c = 'a';

cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c <<std:: endl;

//没有包含<stdio.h>,也可以使用print和scanf,在包含<iostream>间接包含了
//vs编译器是这样的,其他编译器可能会报错
scanf("%d%lf", &a, &b);
printf("%d %lf\n", a, b);

//可以自动识别类型
cin >> a;
cin >> b >> c;

cout << a << endl;
cout << b << " " << c << endl;

return 0;
}

在io需求比较高的地方,如部分⼤大量输入的竞赛题中,加上以下3行代码,可以提高C++IO效率

#include<iostream>
using namespace std;
int main()
{
// 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码
// 可以提⾼C++IO效率
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}

四 . 缺省参数

1 . 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)

2 . 全缺省就是全部形参给缺省值 , 半缺省就是部分形参给缺省值 。 C++ 规定半缺省参数必须从右往左依次连续缺省 , 不能间隔跳跃给缺省值 。 

3 . 带缺省参数的函数调用 , C++规定必须从左到右依次给实参 , 不能跳跃给实参 。

4 . 函数声明和定义分离时 , 缺省参数不能在函数声明和定义中同时出现 规定必须函数声明给缺省值 。 

#include<iostream>
using namespace std;

//全缺省
void Func1(int a = 10,int b = 20,int c=30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}

//半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}

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

Func2(100);
Func2(100,200);
Func2(100,200,300);
return 0;
}

五 . 函数重载

C++支持在同一作用域出现同名函数 , 但是要求这些同名函数的形参不同 , 可以是参数个数不同或者类型不同 。 这样C++函数调用就表现出了多态行为,使用更灵活 。 C语言是不支持同一作用域中出现同名函数的 。 

----> 同名函数,要求形参不同  (参数不同  或者  类型不同 )

#include<iostream>
 using namespace std;

// 1、参数类型不同
 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;
 }

// 2、参数个数不同
  void f()
 {
     cout << "f()" << endl;
 }

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


// 3、参数类型顺序不同
  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 f1()
 {
 cout << "f()" << endl;
 }

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

int main()
{
 Add(10, 20);
 Add(10.1, 20.2);

 f();
 f(10);

 f(10, 'a');
 f('a', 10);

 return 0;
}

 返回值不同不能作为重载条件 , 因为调用时也无法区分 , 会报错 , 存在歧义 , 编译不知道调用谁 !

void fxx()
{
}
int fxx()
{
return 0;
}

六 . 引用

6.1 引用的概念和定义

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

  格式 :

类型& 引用别名 = 引用对象 ;

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

int main()
{
int a = 0;

//引用 : b 和 c 是 a 的别名
int& b = a;
int& c = a;

//也可以给别名取别名
int& d = b;

++d;

//指向同一块的空间
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}

 这里引用可以拿水浒传中的李逵作比 , 宋江叫他“铁牛” , 江湖人称 " 黑旋风“ ;铁牛,李逵,黑旋风都是同一个人 ;

C++ 中为了避免引入太多的运算符 , 会复用C 的一些符号 ,比如前面的 << 和 >> , 这里引用  也和  取地址  使用了同一个符号 & , 大家注意区分

 6.2 引用的特性

  • 引用在定义时 , 必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体 , 就不能再引用其他实体

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

int main()
{
int a = 0;

int& b = a;
int& c = a;
int& d = b;

int e = 10;
b = e;

cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
cout << e << endl;

cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
cout << &e << endl << endl;
return 0;
}

 6.3 引用的使用

1 . 引用在实践中主要是用于引用传参 和 引用返回值减少拷贝提高效率改变引用对象时同时改变被引用对象

2 . 引用传参跟指针传参功能是类似的,引用传参相对更方便一些。

3 . 引用返回值的场景相对比较复杂 , 这里简单介绍以下场景 , 在后续的类和对象的博客中,还会继续深入介绍 。

4 . 引用 和  指针 在实践中相辅相成,功能有重叠性 , 但是各有特点 , 互相不可替代 。 C++的引用跟其他语言的引用 ( 如Java ) 是有很大的区别的 , 除了用法 , 最大的点 , C++引用定义后不能改变指向 , Java 引用可以改变指向 。 

1 . 下面举一个我们熟悉的交换函数Swap , 来体会以下 引用  的 ”巧“ :

指针传参版本 :


#include<iostream>
using namespace std;

void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int x = 1;
int y = 2;
cout << "交换之前:x = " << x << " y = " << y << endl;
Swap(&x, &y);
cout << "交换之后:x = " << x << " y = " << y << endl;
return 0;
}

引用传参版本:

void Swap(int& x, int& y)
{
int  tmp = x;
x = y;
y = tmp;
}
int main()
{
int x = 1;
int y = 2;
cout << "交换之前:x = " << x << " y = " << y << endl;
Swap(x, y);
cout << "交换之后:x = " << x << " y = " << y << endl;
return 0;
}

 2 . 紧接着再来看看之前实现过的数据结构 -- 栈 , 使用引用传参 :
1 ) 引用后 , 形参与实参指向的空间是同一个 , 传参的时候不需要传递地址

2 ) 访问结构体的时候 不使用 -> 操作符 而使用 . 操作符

如果想使用引用传参来实现数据结构栈 , 可以看之前博客 : 栈与队列的常见接口的实现-CSDN博客 , 试着把指针传参改为引用传参

#include <assert.h>
#include<iostream>
using namespace std;


typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
// 栈顶
void STPush(ST & rs, STDataType x)
{
 // 满了, 扩容
if (rs.top == rs.capacity)
 {
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType * tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}

// int STTop(ST& rs)
int& STTop(ST & rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
}

int main()
{
// 调⽤全局的
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);

cout << STTop(st1) << endl;

STTop(st1) += 10;
cout << STTop(st1) << endl;
return 0;
}

3 . 一些主要用C代码实现版本数据结构的教材中 , 会使用C++引用传参 , 来代替指针传参 , 目的是简化程序 , 避开复杂指针 ---> 一级指针 , 二级指针 ...;

//使用引用来代替一级指针
typedef struct SeqList
{
 int a[10];
 int size;
 }SLT;

// ⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序,避开复杂的指针
void SeqPushBack(SLT& sl, int x)
{

}

//void SeqPushBack(SLT* sl, int x);
//void SeqPushBack(SLT& sl, int x)
//使用引用来替代二级指针
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)

#include<iostream>
using namespace std;


typedef struct ListNode
{
 int val;
 struct ListNode* next;
}LTNode, *PNode;

// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序


void ListPushBack(PNode& phead, int x)
{
 PNode newnode = (PNode)malloc(sizeof(LTNode));
 newnode->val = x;
 newnode->next = NULL;
    if (phead == NULL)
    {
        phead = newnode;
    }
       else
   {
 //...
   }
}

int main()
{
    PNode plist = NULL;
    ListPushBack(plist, 1);
    return 0;
}

注意 : 这里的Pnode 是结构体指针变量 。对于二级指针引用 ,拿 int 型做比, 只能是 

---->  int* & 引用名 = 引用对象 ;( 为整型指针变量给别名)

不会有 int & *  引用名 = 引用对象 ,这样的定义,不符合引用的格式

4 .  引用返回值

下面的STTop(S) += 10 , 会报错 ,因为函数返回的时候 , 返回的是临时对象 , 具有常性 , 不可以更改 !

如果想要实现 STTop(S) += 10 , 使用引用 , 返回对象的别名

为什么需要有临时对象的概念?直接返回不行吗? 

----> 下面的代码 , top 如果出了作用域 , 生命周期结束了,不使用 临时对象/寄存器存储 , top 就找不到了

6.4 const 引用

1 . 可以引用一个const 对象 , 但是必须用 const 引用 。 const 引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小 , 但是不能放大

2 . 需要注意的是  类似 :
 int & rb = a*3 ;          double d = 12.34 ;      int& rd = d ;

这样一些场景下 a* 3的结果保存在一个临时对象中 , int& rd = d 也是类似 , 在类型转换中会产生临时对象存储中间值 , 也就是 , rb 和 rd 引用的都是临时对象 , 而 C++ 规定临时对象具有常性 , 所以这里就触发了权限放大 , 必须要用常引用才可以 。

3 . 所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时 -----> 临时创建的一个未命名的对象 , C++中把这个未命名的对象叫做临时对象 。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

int main()
{
const int a = 10;

//权限不可以放大
//int& ra = a;

//权限可以平移
const int& ra = a;

//权限可以缩小
int b = 20;
const int& rb = b;
return 0;
}


//引用传参,减少拷贝,建议用引用传参,好处是实参可以是常量/运算表达式等
void Func(const int& r)
{

}
int main()
{
int a = 10;
const int& ra = a;
const int& rra = 30;

const int& rb = a * 3;

double d = 13.14;
const int& rd = d;
Func(a);
Func(10);
Func(a * 3);
Func(d);

return 0;
}

6.5 指针和引用的关系(笔试常考)

C++中指针和引用就像两个性格迥异的亲兄弟 , 指针是哥哥 , 引用是弟弟 , 在实践中 , 他们相辅相成 , 功能具有重叠性 , 但是各有自己的特点 ,互相不可以替代 。

  1. 语法概念上 : 引用是一个变量的取别名 ---> 不开空间 , 指针是存储一个变量的地址 , 需要开辟空间
  2. 引用在定义时必须初始化,指针建议初始化,但是在语法上不是必须的。
  3. 引用初始化时引用一个对象  , 就不能再引用其他对象 ; 而指针可以在不断地改变指向对象 。
  4. 引用可以直接访问指向对象 , 指针需要解引用才能访问指向对象。 
  5. sizeof 中含义不同 ,  引用结果为引用类型的大小 , 但指针始终是地址空间所占字节个数(32位平台下 --> 4个字节 , 64 位平台下 8 字节)
  6. 指针很容易出现空指针和野指针的问题 , 引用很少出现 , 引用使用起来相对更安全一些 。 

原文地址:https://blog.csdn.net/khjjjgd/article/details/143629081

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!