当前位置 : 主页 > 编程语言 > c语言 >

【C++入门】初阶基础(一) 详解

来源:互联网 收集:自由互联 发布时间:2023-09-07
1.命名空间 目的:解决c语言的缺陷,命名冲突。 #includestdio.hint rand=0;int main(){ printf("%d",rand);} 上面这段程序是可以运行的 但是! #includestdio.h#includestdlib.hint rand=0;int main(){ printf("%d",rand

1.命名空间

目的:解决c语言的缺陷,命名冲突。

#include<stdio.h>

int rand=0;
int main()
{
    
  printf("%d",rand);
}

上面这段程序是可以运行的

但是!

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

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

那么上面的代码就会报错。因为stdlib.h 那么包含了rand这个函数 ,我们重新定义rand 那么出现报错。命名被重复定义了。

1.1命名空间定义一个域

关键字 namespace +空间名

namespace std
// namespace name
{
    string a="zhangsan";
}

1.2访问全局域和命名空间作用域

//全局域
int a=0;
//命名空间
namespace bit
{
  int a=1;

}
//局部域
int main()
{
  int a=2;
  
  printf("%d\n",::a);   //访问的全局域 
   printf("%d\n",a);  //访问局部域2
  printf("%d\n",bit::a); //访问命名空间
}

变量搜索顺序:局部域→全局域→展开命名空间or指定访问命名空间域。

展开命名空间 ------→相当于把变量暴露到了全局 ,编译时是否去命名空间搜索

using namespace std; //可以直接使用命名空间的变量不重复

1.3 定义命名空间

命名空间内可以定义函数、变量、结构体、和再嵌套命名空间。

namespace bit
{
 // 命名空间中可以定义变量/函数/类型
 int a = 0;
 int b = 1;
 int Add(int left, int right)
 {
    return left + right;
 }
 struct Node
 {
    struct Node* next;
    int val;
 };
}
int main()
{
    // 编译报错:error C2065: “a”: 未声明的标识符
    printf("%d\n", a);
    return 0;
}

1.4 命名空间的合并

当我们在多个头文件里面定义了同名的命名空间,当我们调用这些头文件的时候会进行合并两个定义的变量,结构体、函数、嵌套的命名空间,切记不要重复定义变量和函数,可以选择再嵌套一个命名空间来包含这个变量。

1.5 注意事项

using namespace std;

直接展开会有风险,我们定义如果和库重名,就报错了。

建议项目不要直接展开,还是推荐直接指定来访问。

那么问题来了,项目里面我都去指定?

解决办法:把常用展开,只是展开部分

using std::cout;
using std::endl;

2.C++输入&输出

<< 叫流插入运算符

叫流提取运算符

cout<<"hello heshiqiang"<<endl;

cin>>x;

关键字 :cout cin

特性:自动识别类型 ,可以不用定义类型

作用:输入输出

注意:cin自动识别精度到小数点后一位。 当我们要输入和输出小数点后2位,那么还是乖乖用printf和scanf来控制输入输出。

3.函数重载

c中还是不能进行函数重载。同一作用域下c++中可以重复定义多个同名的函数,但是有一定的要求,如下:

  • 函数名相同,参数类型顺序不同
  • 函数名相同,参数个数不同
#include<iostream>
using namespace std;
//第一个add
int add(int a, int b)
{
	return a + b;


}
//第二个add
void add(double a, double b)
{
	printf("%lf\n", a + b);
}
int main()
{
	//函数重载
	int ret = add(10, 20);
	printf("%d\n", ret);
	add(10.0, 20.0);
    //这里体现的是函数名相同,参数类型顺序不同
    //同理 我们可以改变参数个数 一样可以调用同名函数


	return 0;
}

注意: 改变函数的返回值是无法进行函数重载,必须是上面规定的两条。

那么调用速度会不会变慢?

编译之后,代码变成指令,通过指令找到函数的地址,速度还是很快的,那么如何找到呢,我们打开反汇编指令查看可以发现,c++对函数名进行了修饰,,call找到修饰之后的地址,实现这个函数重载功能。

c++调用函数时候add之后进行了修饰,call指令通过修饰后的地址找到这个函数

那么如何修饰的呢?

第一个add: XXaddii(ii,就是int int)

第二个add:XXadddd(dd,就是double double)

这也是为什么顺序不同,找到的函数会不一样

4.引用

4.1 引用概念

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

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

相当于给人取别名

&

1.取地址

例如:scanf("%d",&a);

2.引用

例如:int& k=i;
  • &放到变量前面,那么就是取地址
  • &放到数据类型的后面,那么就是引用

下面就是演示代码

int main()
{
	int a = 10;
	int& k = a;
	int b = a;

	cout << &a << endl;
	cout << &k << endl;
	cout << &b << endl;
	return 0;
}
//实现结果

【C++入门】初阶基础(一) 详解_基础语法

可以看到就是a就是k,k就是i。那么a++了,k也就++了,不影响b。

4.1.1引用特性总结


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

4.2 多层引用问题

//重新给a取一个别名 这个是合理的
//我们允许一个变量有多个别名
int& m=a;
//这个也是被允许的 m是a的别名 那么给m再起一个别名
int& j=m;  // x

那么我们可以推广到指针的别名,结构体的别名,函数的别名

4.3使用场景

4.3.1做参数(输出型参数)

1.形参的改变要改变实参

2.提高效率(大对象/深拷贝)

void Swap(int& a,int& b)
{
  int temp=a;
  a=b;
  b=temp;
}
#include <time.h>
#include<iostream>
using namespace std;
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;
}

int main()
{
	TestRefAndValue();

	return 0;
}

运行截图:

【C++入门】初阶基础(一) 详解_基础语法_02

4.3.2做返回值
  • 常用的传值返回(没有用&)
int add(int a,int b)
{
  return a+b;//返回的时候如果是小量数据那么就会被放到寄存器里面 开一个临时变量
  //来存储这个临时拷贝
}
  • 引用返回

注意事项:就是函数的放回,出了作用域,如果返回的对象没有还给系统,那么才能使用引用传值。(如何保持出了定义的函数作用域还不还给系统,那么我们一般定义全局变量)

#include<iostream>
using namespace std;
int c=0;
int& add(int a,int b)
{
  int c=a+b;
  return c;
}
int main()
{
  int a=add(10,10);
}
  • 在返回大对象的时候,引用返回和常用的传值返回比较
#include <time.h>
#include<iostream>
using namespace std;
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
 // 以值作为函数的返回值类型
 size_t begin1 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc1();
 size_t end1 = clock();
 // 以引用作为函数的返回值类型
 size_t begin2 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc2();
 size_t end2 = clock();
 // 计算两个函数运算完成之后的时间
 cout << "TestFunc1 time:" << end1 - begin1 << endl;
 cout << "TestFunc2 time:" << end2 - begin2 << endl;
}


int main()
{
  TestReturnByRefOrValue();
}

运行结果:

【C++入门】初阶基础(一) 详解_基础语法_03

通过测试,传值和用引用在效率方面有很大的区别。引用效率更高。

引用返回值的时候权限的改变

int func1()
{
	static int x = 0;
	return x;
}
int& func2()
{
	static int x = 0;
	return x;
}

int main()
{
	//const int& ret1 = func1();  //权限平移
	//int& ret1 = func1(); //权限放大
	//int ret1 = func1(); //拷贝

	//int& ret2 = func2(); //权限平移
	//const int& ret2 = func2();  //权限缩小 被const修饰具有常性不可修改
    
  
    //出现整型提升 整型转换 就一定会生成一个临时变量来先整型提升或者转换再赋值
    
    double dd=1.11;
    int ii=dd;
    const int & rii=dd;
    //在发生整型提升或者整型转换 都会生成一个临时变量,然后就是把临时变量放到rii里面
//	//然后 临时变量具有常性   const修饰之后也有常性

	return 0;
}

4.4 引用和指针之间的区别

  1. 引用是和原来的实体共用一个空间,相当于还是同一个人,只是不同的名称,不需要开辟空间。
  2. 引用是按照指针的方式实现的。
int main()
{
    int a = 10;
  
  //语法层面:不开空间,是对a取别名
    int& ra = a;
    ra = 20;
  //语法层面:开空间,存储a的地址
    int* pa = &a;
    *pa = 20;
    return 0;
}

上面一段代码的汇编代码对比

【C++入门】初阶基础(一) 详解_基础语法_04

我们可以通过观察发现,底层实现原理是一致的。

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全

10.权限不能放大,例如 const int a=0; int& b=a; 这个地方就是错误的

引用总结: 1.、基本任何场景都可以用引用传参。

2、谨慎使用

5.auto关键字特性和使用

我个人认为auto挺好用的,不用自己推断数据类型,编译器自动帮我推断,当写工程的时候,可以避免因为单词拼写错误出现的报错。

5.1定义

auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

5.2auto的特性

1.可以推导初始化的数据类型

2.要想使用auto必须进行初始化

3.可以同行声明多个变量,但是变量数据类型要一致

5.3auto的使用

#include<iostream>
using namespace std;
int main()
{
    auto a=10;   //初始化 并能推断出a的类型为int
    auto b=10,c=11; //同行定义多个变量 多个变量数据类型是一致的
  
    cout<<a<<" "<<b<<" "<<c<<" "<<endl;
  
    return 0;
}
int main()
{
	//int a = 0;
	//int b = a;
	//auto c = a;   //根据右边表达式的类型推导c的类型

	//auto d = c + 1.11;     
	//cout << typeid(c).name() << endl;
	//cout << typeid(d).name() << endl;

	vector<int> v;
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//简化使用迭代器
	auto a = v.begin();
	//遍历数组读出数组内容
	for (auto i : arr)
	{
		cout << i << " " << endl;
	}

	//修改数组里面的内容
	for (auto& i : arr)
	{
		i *= 2;
	}
	return 0;
}
上一篇:【算法与数据结构】排序详解(C语言)
下一篇:没有了
网友评论